1 /* build: `node build.js modules=ALL exclude=json,gestures minifier=uglifyjs` */ 2 /*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */ 3 4 var fabric = fabric || { version: "1.6.0" }; 5 if (typeof exports !== 'undefined') { 6 exports.fabric = fabric; 7 } 8 9 if (typeof document !== 'undefined' && typeof window !== 'undefined') { 10 fabric.document = document; 11 fabric.window = window; 12 // ensure globality even if entire library were function wrapped (as in Meteor.js packaging system) 13 window.fabric = fabric; 14 } 15 else { 16 // assume we're running under node.js when document/window are not present 17 fabric.document = require("jsdom") 18 .jsdom("<!DOCTYPE html><html><head></head><body></body></html>"); 19 20 if (fabric.document.createWindow) { 21 fabric.window = fabric.document.createWindow(); 22 } else { 23 fabric.window = fabric.document.parentWindow; 24 } 25 } 26 27 /** 28 * True when in environment that supports touch events 29 * @type boolean 30 */ 31 fabric.isTouchSupported = "ontouchstart" in fabric.document.documentElement; 32 33 /** 34 * True when in environment that's probably Node.js 35 * @type boolean 36 */ 37 fabric.isLikelyNode = typeof Buffer !== 'undefined' && 38 typeof window === 'undefined'; 39 40 /* _FROM_SVG_START_ */ 41 /** 42 * Attributes parsed from all SVG elements 43 * @type array 44 */ 45 fabric.SHARED_ATTRIBUTES = [ 46 "display", 47 "transform", 48 "fill", "fill-opacity", "fill-rule", 49 "opacity", 50 "stroke", "stroke-dasharray", "stroke-linecap", 51 "stroke-linejoin", "stroke-miterlimit", 52 "stroke-opacity", "stroke-width", 53 "id" 54 ]; 55 /* _FROM_SVG_END_ */ 56 57 /** 58 * Pixel per Inch as a default value set to 96. Can be changed for more realistic conversion. 59 */ 60 fabric.DPI = 96; 61 fabric.reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)'; 62 fabric.fontPaths = { }; 63 64 /** 65 * Device Pixel Ratio 66 * @see https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/HTML-canvas-guide/SettingUptheCanvas/SettingUptheCanvas.html 67 */ 68 fabric.devicePixelRatio = fabric.window.devicePixelRatio || 69 fabric.window.webkitDevicePixelRatio || 70 fabric.window.mozDevicePixelRatio || 71 1; 72 73 74 (function() { 75 76 /** 77 * @private 78 * @param {String} eventName 79 * @param {Function} handler 80 */ 81 function _removeEventListener(eventName, handler) { 82 if (!this.__eventListeners[eventName]) { 83 return; 84 } 85 86 if (handler) { 87 fabric.util.removeFromArray(this.__eventListeners[eventName], handler); 88 } 89 else { 90 this.__eventListeners[eventName].length = 0; 91 } 92 } 93 94 /** 95 * Observes specified event 96 * @deprecated `observe` deprecated since 0.8.34 (use `on` instead) 97 * @memberOf fabric.Observable 98 * @alias on 99 * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler}) 100 * @param {Function} handler Function that receives a notification when an event of the specified type occurs 101 * @return {Self} thisArg 102 * @chainable 103 */ 104 function observe(eventName, handler) { 105 if (!this.__eventListeners) { 106 this.__eventListeners = { }; 107 } 108 // one object with key/value pairs was passed 109 if (arguments.length === 1) { 110 for (var prop in eventName) { 111 this.on(prop, eventName[prop]); 112 } 113 } 114 else { 115 if (!this.__eventListeners[eventName]) { 116 this.__eventListeners[eventName] = [ ]; 117 } 118 this.__eventListeners[eventName].push(handler); 119 } 120 return this; 121 } 122 123 /** 124 * Stops event observing for a particular event handler. Calling this method 125 * without arguments removes all handlers for all events 126 * @deprecated `stopObserving` deprecated since 0.8.34 (use `off` instead) 127 * @memberOf fabric.Observable 128 * @alias off 129 * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler}) 130 * @param {Function} handler Function to be deleted from EventListeners 131 * @return {Self} thisArg 132 * @chainable 133 */ 134 function stopObserving(eventName, handler) { 135 if (!this.__eventListeners) { 136 return; 137 } 138 139 // remove all key/value pairs (event name -> event handler) 140 if (arguments.length === 0) { 141 this.__eventListeners = { }; 142 } 143 // one object with key/value pairs was passed 144 else if (arguments.length === 1 && typeof arguments[0] === 'object') { 145 for (var prop in eventName) { 146 _removeEventListener.call(this, prop, eventName[prop]); 147 } 148 } 149 else { 150 _removeEventListener.call(this, eventName, handler); 151 } 152 return this; 153 } 154 155 /** 156 * Fires event with an optional options object 157 * @deprecated `fire` deprecated since 1.0.7 (use `trigger` instead) 158 * @memberOf fabric.Observable 159 * @alias trigger 160 * @param {String} eventName Event name to fire 161 * @param {Object} [options] Options object 162 * @return {Self} thisArg 163 * @chainable 164 */ 165 function fire(eventName, options) { 166 if (!this.__eventListeners) { 167 return; 168 } 169 170 var listenersForEvent = this.__eventListeners[eventName]; 171 if (!listenersForEvent) { 172 return; 173 } 174 175 for (var i = 0, len = listenersForEvent.length; i < len; i++) { 176 // avoiding try/catch for perf. reasons 177 listenersForEvent[i].call(this, options || { }); 178 } 179 return this; 180 } 181 182 /** 183 * @namespace fabric.Observable 184 * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#events} 185 * @see {@link http://fabricjs.com/events/|Events demo} 186 */ 187 fabric.Observable = { 188 observe: observe, 189 stopObserving: stopObserving, 190 fire: fire, 191 192 on: observe, 193 off: stopObserving, 194 trigger: fire 195 }; 196 })(); 197 198 199 /** 200 * @namespace fabric.Collection 201 */ 202 fabric.Collection = { 203 204 /** 205 * Adds objects to collection, then renders canvas (if `renderOnAddRemove` is not `false`) 206 * Objects should be instances of (or inherit from) fabric.Object 207 * @param {...fabric.Object} object Zero or more fabric instances 208 * @return {Self} thisArg 209 */ 210 add: function () { 211 this._objects.push.apply(this._objects, arguments); 212 for (var i = 0, length = arguments.length; i < length; i++) { 213 this._onObjectAdded(arguments[i]); 214 } 215 this.renderOnAddRemove && this.renderAll(); 216 return this; 217 }, 218 219 /** 220 * Inserts an object into collection at specified index, then renders canvas (if `renderOnAddRemove` is not `false`) 221 * An object should be an instance of (or inherit from) fabric.Object 222 * @param {Object} object Object to insert 223 * @param {Number} index Index to insert object at 224 * @param {Boolean} nonSplicing When `true`, no splicing (shifting) of objects occurs 225 * @return {Self} thisArg 226 * @chainable 227 */ 228 insertAt: function (object, index, nonSplicing) { 229 var objects = this.getObjects(); 230 if (nonSplicing) { 231 objects[index] = object; 232 } 233 else { 234 objects.splice(index, 0, object); 235 } 236 this._onObjectAdded(object); 237 this.renderOnAddRemove && this.renderAll(); 238 return this; 239 }, 240 241 /** 242 * Removes objects from a collection, then renders canvas (if `renderOnAddRemove` is not `false`) 243 * @param {...fabric.Object} object Zero or more fabric instances 244 * @return {Self} thisArg 245 * @chainable 246 */ 247 remove: function() { 248 var objects = this.getObjects(), 249 index; 250 251 for (var i = 0, length = arguments.length; i < length; i++) { 252 index = objects.indexOf(arguments[i]); 253 254 // only call onObjectRemoved if an object was actually removed 255 if (index !== -1) { 256 objects.splice(index, 1); 257 this._onObjectRemoved(arguments[i]); 258 } 259 } 260 261 this.renderOnAddRemove && this.renderAll(); 262 return this; 263 }, 264 265 /** 266 * Executes given function for each object in this group 267 * @param {Function} callback 268 * Callback invoked with current object as first argument, 269 * index - as second and an array of all objects - as third. 270 * Iteration happens in reverse order (for performance reasons). 271 * Callback is invoked in a context of Global Object (e.g. `window`) 272 * when no `context` argument is given 273 * 274 * @param {Object} context Context (aka thisObject) 275 * @return {Self} thisArg 276 */ 277 forEachObject: function(callback, context) { 278 var objects = this.getObjects(), 279 i = objects.length; 280 while (i--) { 281 callback.call(context, objects[i], i, objects); 282 } 283 return this; 284 }, 285 286 /** 287 * Returns an array of children objects of this instance 288 * Type parameter introduced in 1.3.10 289 * @param {String} [type] When specified, only objects of this type are returned 290 * @return {Array} 291 */ 292 getObjects: function(type) { 293 if (typeof type === 'undefined') { 294 return this._objects; 295 } 296 return this._objects.filter(function(o) { 297 return o.type === type; 298 }); 299 }, 300 301 /** 302 * Returns object at specified index 303 * @param {Number} index 304 * @return {Self} thisArg 305 */ 306 item: function (index) { 307 return this.getObjects()[index]; 308 }, 309 310 /** 311 * Returns true if collection contains no objects 312 * @return {Boolean} true if collection is empty 313 */ 314 isEmpty: function () { 315 return this.getObjects().length === 0; 316 }, 317 318 /** 319 * Returns a size of a collection (i.e: length of an array containing its objects) 320 * @return {Number} Collection size 321 */ 322 size: function() { 323 return this.getObjects().length; 324 }, 325 326 /** 327 * Returns true if collection contains an object 328 * @param {Object} object Object to check against 329 * @return {Boolean} `true` if collection contains an object 330 */ 331 contains: function(object) { 332 return this.getObjects().indexOf(object) > -1; 333 }, 334 335 /** 336 * Returns number representation of a collection complexity 337 * @return {Number} complexity 338 */ 339 complexity: function () { 340 return this.getObjects().reduce(function (memo, current) { 341 memo += current.complexity ? current.complexity() : 0; 342 return memo; 343 }, 0); 344 } 345 }; 346 347 348 (function(global) { 349 350 var sqrt = Math.sqrt, 351 atan2 = Math.atan2, 352 pow = Math.pow, 353 abs = Math.abs, 354 PiBy180 = Math.PI / 180; 355 356 /** 357 * @namespace fabric.util 358 */ 359 fabric.util = { 360 361 /** 362 * Removes value from an array. 363 * Presence of value (and its position in an array) is determined via `Array.prototype.indexOf` 364 * @static 365 * @memberOf fabric.util 366 * @param {Array} array 367 * @param {Any} value 368 * @return {Array} original array 369 */ 370 removeFromArray: function(array, value) { 371 var idx = array.indexOf(value); 372 if (idx !== -1) { 373 array.splice(idx, 1); 374 } 375 return array; 376 }, 377 378 /** 379 * Returns random number between 2 specified ones. 380 * @static 381 * @memberOf fabric.util 382 * @param {Number} min lower limit 383 * @param {Number} max upper limit 384 * @return {Number} random value (between min and max) 385 */ 386 getRandomInt: function(min, max) { 387 return Math.floor(Math.random() * (max - min + 1)) + min; 388 }, 389 390 /** 391 * Transforms degrees to radians. 392 * @static 393 * @memberOf fabric.util 394 * @param {Number} degrees value in degrees 395 * @return {Number} value in radians 396 */ 397 degreesToRadians: function(degrees) { 398 return degrees * PiBy180; 399 }, 400 401 /** 402 * Transforms radians to degrees. 403 * @static 404 * @memberOf fabric.util 405 * @param {Number} radians value in radians 406 * @return {Number} value in degrees 407 */ 408 radiansToDegrees: function(radians) { 409 return radians / PiBy180; 410 }, 411 412 /** 413 * Rotates `point` around `origin` with `radians` 414 * @static 415 * @memberOf fabric.util 416 * @param {fabric.Point} point The point to rotate 417 * @param {fabric.Point} origin The origin of the rotation 418 * @param {Number} radians The radians of the angle for the rotation 419 * @return {fabric.Point} The new rotated point 420 */ 421 rotatePoint: function(point, origin, radians) { 422 point.subtractEquals(origin); 423 var v = fabric.util.rotateVector(point, radians); 424 return new fabric.Point(v.x, v.y).addEquals(origin); 425 }, 426 427 /** 428 * Rotates `vector` with `radians` 429 * @static 430 * @memberOf fabric.util 431 * @param {Object} vector The vector to rotate (x and y) 432 * @param {Number} radians The radians of the angle for the rotation 433 * @return {Object} The new rotated point 434 */ 435 rotateVector: function(vector, radians) { 436 var sin = Math.sin(radians), 437 cos = Math.cos(radians), 438 rx = vector.x * cos - vector.y * sin, 439 ry = vector.x * sin + vector.y * cos; 440 return { 441 x: rx, 442 y: ry 443 }; 444 }, 445 446 /** 447 * Apply transform t to point p 448 * @static 449 * @memberOf fabric.util 450 * @param {fabric.Point} p The point to transform 451 * @param {Array} t The transform 452 * @param {Boolean} [ignoreOffset] Indicates that the offset should not be applied 453 * @return {fabric.Point} The transformed point 454 */ 455 transformPoint: function(p, t, ignoreOffset) { 456 if (ignoreOffset) { 457 return new fabric.Point( 458 t[0] * p.x + t[2] * p.y, 459 t[1] * p.x + t[3] * p.y 460 ); 461 } 462 return new fabric.Point( 463 t[0] * p.x + t[2] * p.y + t[4], 464 t[1] * p.x + t[3] * p.y + t[5] 465 ); 466 }, 467 468 /** 469 * Returns coordinates of points's bounding rectangle (left, top, width, height) 470 * @param {Array} points 4 points array 471 * @return {Object} Object with left, top, width, height properties 472 */ 473 makeBoundingBoxFromPoints: function(points) { 474 var xPoints = [points[0].x, points[1].x, points[2].x, points[3].x], 475 minX = fabric.util.array.min(xPoints), 476 maxX = fabric.util.array.max(xPoints), 477 width = Math.abs(minX - maxX), 478 yPoints = [points[0].y, points[1].y, points[2].y, points[3].y], 479 minY = fabric.util.array.min(yPoints), 480 maxY = fabric.util.array.max(yPoints), 481 height = Math.abs(minY - maxY); 482 483 return { 484 left: minX, 485 top: minY, 486 width: width, 487 height: height 488 }; 489 }, 490 491 /** 492 * Invert transformation t 493 * @static 494 * @memberOf fabric.util 495 * @param {Array} t The transform 496 * @return {Array} The inverted transform 497 */ 498 invertTransform: function(t) { 499 var a = 1 / (t[0] * t[3] - t[1] * t[2]), 500 r = [a * t[3], -a * t[1], -a * t[2], a * t[0]], 501 o = fabric.util.transformPoint({ x: t[4], y: t[5] }, r, true); 502 r[4] = -o.x; 503 r[5] = -o.y; 504 return r; 505 }, 506 507 /** 508 * A wrapper around Number#toFixed, which contrary to native method returns number, not string. 509 * @static 510 * @memberOf fabric.util 511 * @param {Number|String} number number to operate on 512 * @param {Number} fractionDigits number of fraction digits to "leave" 513 * @return {Number} 514 */ 515 toFixed: function(number, fractionDigits) { 516 return parseFloat(Number(number).toFixed(fractionDigits)); 517 }, 518 519 /** 520 * Converts from attribute value to pixel value if applicable. 521 * Returns converted pixels or original value not converted. 522 * @param {Number|String} value number to operate on 523 * @return {Number|String} 524 */ 525 parseUnit: function(value, fontSize) { 526 var unit = /\D{0,2}$/.exec(value), 527 number = parseFloat(value); 528 if (!fontSize) { 529 fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE; 530 } 531 switch (unit[0]) { 532 case 'mm': 533 return number * fabric.DPI / 25.4; 534 535 case 'cm': 536 return number * fabric.DPI / 2.54; 537 538 case 'in': 539 return number * fabric.DPI; 540 541 case 'pt': 542 return number * fabric.DPI / 72; // or * 4 / 3 543 544 case 'pc': 545 return number * fabric.DPI / 72 * 12; // or * 16 546 547 case 'em': 548 return number * fontSize; 549 550 default: 551 return number; 552 } 553 }, 554 555 /** 556 * Function which always returns `false`. 557 * @static 558 * @memberOf fabric.util 559 * @return {Boolean} 560 */ 561 falseFunction: function() { 562 return false; 563 }, 564 565 /** 566 * Returns klass "Class" object of given namespace 567 * @memberOf fabric.util 568 * @param {String} type Type of object (eg. 'circle') 569 * @param {String} namespace Namespace to get klass "Class" object from 570 * @return {Object} klass "Class" 571 */ 572 getKlass: function(type, namespace) { 573 // capitalize first letter only 574 type = fabric.util.string.camelize(type.charAt(0).toUpperCase() + type.slice(1)); 575 return fabric.util.resolveNamespace(namespace)[type]; 576 }, 577 578 /** 579 * Returns object of given namespace 580 * @memberOf fabric.util 581 * @param {String} namespace Namespace string e.g. 'fabric.Image.filter' or 'fabric' 582 * @return {Object} Object for given namespace (default fabric) 583 */ 584 resolveNamespace: function(namespace) { 585 if (!namespace) { 586 return fabric; 587 } 588 589 var parts = namespace.split('.'), 590 len = parts.length, 591 obj = global || fabric.window; 592 593 for (var i = 0; i < len; ++i) { 594 obj = obj[parts[i]]; 595 } 596 597 return obj; 598 }, 599 600 /** 601 * Loads image element from given url and passes it to a callback 602 * @memberOf fabric.util 603 * @param {String} url URL representing an image 604 * @param {Function} callback Callback; invoked with loaded image 605 * @param {Any} [context] Context to invoke callback in 606 * @param {Object} [crossOrigin] crossOrigin value to set image element to 607 */ 608 loadImage: function(url, callback, context, crossOrigin) { 609 if (!url) { 610 callback && callback.call(context, url); 611 return; 612 } 613 614 var img = fabric.util.createImage(); 615 616 /** @ignore */ 617 img.onload = function () { 618 callback && callback.call(context, img); 619 img = img.onload = img.onerror = null; 620 }; 621 622 /** @ignore */ 623 img.onerror = function() { 624 fabric.log('Error loading ' + img.src); 625 callback && callback.call(context, null, true); 626 img = img.onload = img.onerror = null; 627 }; 628 629 // data-urls appear to be buggy with crossOrigin 630 // https://github.com/kangax/fabric.js/commit/d0abb90f1cd5c5ef9d2a94d3fb21a22330da3e0a#commitcomment-4513767 631 // see https://code.google.com/p/chromium/issues/detail?id=315152 632 // https://bugzilla.mozilla.org/show_bug.cgi?id=935069 633 if (url.indexOf('data') !== 0 && crossOrigin) { 634 img.crossOrigin = crossOrigin; 635 } 636 637 img.src = url; 638 }, 639 640 /** 641 * Creates corresponding fabric instances from their object representations 642 * @static 643 * @memberOf fabric.util 644 * @param {Array} objects Objects to enliven 645 * @param {Function} callback Callback to invoke when all objects are created 646 * @param {String} namespace Namespace to get klass "Class" object from 647 * @param {Function} reviver Method for further parsing of object elements, 648 * called after each fabric object created. 649 */ 650 enlivenObjects: function(objects, callback, namespace, reviver) { 651 objects = objects || [ ]; 652 653 function onLoaded() { 654 if (++numLoadedObjects === numTotalObjects) { 655 callback && callback(enlivenedObjects); 656 } 657 } 658 659 var enlivenedObjects = [ ], 660 numLoadedObjects = 0, 661 numTotalObjects = objects.length; 662 663 if (!numTotalObjects) { 664 callback && callback(enlivenedObjects); 665 return; 666 } 667 668 objects.forEach(function (o, index) { 669 // if sparse array 670 if (!o || !o.type) { 671 onLoaded(); 672 return; 673 } 674 var klass = fabric.util.getKlass(o.type, namespace); 675 if (klass.async) { 676 klass.fromObject(o, function (obj, error) { 677 if (!error) { 678 enlivenedObjects[index] = obj; 679 reviver && reviver(o, enlivenedObjects[index]); 680 } 681 onLoaded(); 682 }); 683 } 684 else { 685 enlivenedObjects[index] = klass.fromObject(o); 686 reviver && reviver(o, enlivenedObjects[index]); 687 onLoaded(); 688 } 689 }); 690 }, 691 692 /** 693 * Groups SVG elements (usually those retrieved from SVG document) 694 * @static 695 * @memberOf fabric.util 696 * @param {Array} elements SVG elements to group 697 * @param {Object} [options] Options object 698 * @return {fabric.Object|fabric.PathGroup} 699 */ 700 groupSVGElements: function(elements, options, path) { 701 var object; 702 703 object = new fabric.PathGroup(elements, options); 704 705 if (typeof path !== 'undefined') { 706 object.setSourcePath(path); 707 } 708 return object; 709 }, 710 711 /** 712 * Populates an object with properties of another object 713 * @static 714 * @memberOf fabric.util 715 * @param {Object} source Source object 716 * @param {Object} destination Destination object 717 * @return {Array} properties Propertie names to include 718 */ 719 populateWithProperties: function(source, destination, properties) { 720 if (properties && Object.prototype.toString.call(properties) === '[object Array]') { 721 for (var i = 0, len = properties.length; i < len; i++) { 722 if (properties[i] in source) { 723 destination[properties[i]] = source[properties[i]]; 724 } 725 } 726 } 727 }, 728 729 /** 730 * Draws a dashed line between two points 731 * 732 * This method is used to draw dashed line around selection area. 733 * See <a href="http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas">dotted stroke in canvas</a> 734 * 735 * @param {CanvasRenderingContext2D} ctx context 736 * @param {Number} x start x coordinate 737 * @param {Number} y start y coordinate 738 * @param {Number} x2 end x coordinate 739 * @param {Number} y2 end y coordinate 740 * @param {Array} da dash array pattern 741 */ 742 drawDashedLine: function(ctx, x, y, x2, y2, da) { 743 var dx = x2 - x, 744 dy = y2 - y, 745 len = sqrt(dx * dx + dy * dy), 746 rot = atan2(dy, dx), 747 dc = da.length, 748 di = 0, 749 draw = true; 750 751 ctx.save(); 752 ctx.translate(x, y); 753 ctx.moveTo(0, 0); 754 ctx.rotate(rot); 755 756 x = 0; 757 while (len > x) { 758 x += da[di++ % dc]; 759 if (x > len) { 760 x = len; 761 } 762 ctx[draw ? 'lineTo' : 'moveTo'](x, 0); 763 draw = !draw; 764 } 765 766 ctx.restore(); 767 }, 768 769 /** 770 * Creates canvas element and initializes it via excanvas if necessary 771 * @static 772 * @memberOf fabric.util 773 * @param {CanvasElement} [canvasEl] optional canvas element to initialize; 774 * when not given, element is created implicitly 775 * @return {CanvasElement} initialized canvas element 776 */ 777 createCanvasElement: function(canvasEl) { 778 canvasEl || (canvasEl = fabric.document.createElement('canvas')); 779 //jscs:disable requireCamelCaseOrUpperCaseIdentifiers 780 if (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') { 781 G_vmlCanvasManager.initElement(canvasEl); 782 } 783 //jscs:enable requireCamelCaseOrUpperCaseIdentifiers 784 return canvasEl; 785 }, 786 787 /** 788 * Creates image element (works on client and node) 789 * @static 790 * @memberOf fabric.util 791 * @return {HTMLImageElement} HTML image element 792 */ 793 createImage: function() { 794 return fabric.isLikelyNode 795 ? new (require('canvas').Image)() 796 : fabric.document.createElement('img'); 797 }, 798 799 /** 800 * Creates accessors (getXXX, setXXX) for a "class", based on "stateProperties" array 801 * @static 802 * @memberOf fabric.util 803 * @param {Object} klass "Class" to create accessors for 804 */ 805 createAccessors: function(klass) { 806 var proto = klass.prototype; 807 808 for (var i = proto.stateProperties.length; i--; ) { 809 810 var propName = proto.stateProperties[i], 811 capitalizedPropName = propName.charAt(0).toUpperCase() + propName.slice(1), 812 setterName = 'set' + capitalizedPropName, 813 getterName = 'get' + capitalizedPropName; 814 815 // using `new Function` for better introspection 816 if (!proto[getterName]) { 817 proto[getterName] = (function(property) { 818 return new Function('return this.get("' + property + '")'); 819 })(propName); 820 } 821 if (!proto[setterName]) { 822 proto[setterName] = (function(property) { 823 return new Function('value', 'return this.set("' + property + '", value)'); 824 })(propName); 825 } 826 } 827 }, 828 829 /** 830 * @static 831 * @memberOf fabric.util 832 * @param {fabric.Object} receiver Object implementing `clipTo` method 833 * @param {CanvasRenderingContext2D} ctx Context to clip 834 */ 835 clipContext: function(receiver, ctx) { 836 ctx.save(); 837 ctx.beginPath(); 838 receiver.clipTo(ctx); 839 ctx.clip(); 840 }, 841 842 /** 843 * Multiply matrix A by matrix B to nest transformations 844 * @static 845 * @memberOf fabric.util 846 * @param {Array} a First transformMatrix 847 * @param {Array} b Second transformMatrix 848 * @param {Boolean} is2x2 flag to multiply matrices as 2x2 matrices 849 * @return {Array} The product of the two transform matrices 850 */ 851 multiplyTransformMatrices: function(a, b, is2x2) { 852 // Matrix multiply a * b 853 return [ 854 a[0] * b[0] + a[2] * b[1], 855 a[1] * b[0] + a[3] * b[1], 856 a[0] * b[2] + a[2] * b[3], 857 a[1] * b[2] + a[3] * b[3], 858 is2x2 ? 0 : a[0] * b[4] + a[2] * b[5] + a[4], 859 is2x2 ? 0 : a[1] * b[4] + a[3] * b[5] + a[5] 860 ]; 861 }, 862 863 /** 864 * Decomposes standard 2x2 matrix into transform componentes 865 * @static 866 * @memberOf fabric.util 867 * @param {Array} a transformMatrix 868 * @return {Object} Components of transform 869 */ 870 qrDecompose: function(a) { 871 var angle = atan2(a[1], a[0]), 872 denom = pow(a[0], 2) + pow(a[1], 2), 873 scaleX = sqrt(denom), 874 scaleY = (a[0] * a[3] - a[2] * a [1]) / scaleX, 875 skewX = atan2(a[0] * a[2] + a[1] * a [3], denom); 876 return { 877 angle: angle / PiBy180, 878 scaleX: scaleX, 879 scaleY: scaleY, 880 skewX: skewX / PiBy180, 881 skewY: 0, 882 translateX: a[4], 883 translateY: a[5] 884 }; 885 }, 886 887 customTransformMatrix: function(scaleX, scaleY, skewX) { 888 var skewMatrixX = [1, 0, abs(Math.tan(skewX * PiBy180)), 1], 889 scaleMatrix = [abs(scaleX), 0, 0, abs(scaleY)]; 890 return fabric.util.multiplyTransformMatrices(scaleMatrix, skewMatrixX, true); 891 }, 892 893 resetObjectTransform: function (target) { 894 target.scaleX = 1; 895 target.scaleY = 1; 896 target.skewX = 0; 897 target.skewY = 0; 898 target.flipX = false; 899 target.flipY = false; 900 target.setAngle(0); 901 }, 902 903 /** 904 * Returns string representation of function body 905 * @param {Function} fn Function to get body of 906 * @return {String} Function body 907 */ 908 getFunctionBody: function(fn) { 909 return (String(fn).match(/function[^{]*\{([\s\S]*)\}/) || {})[1]; 910 }, 911 912 /** 913 * Returns true if context has transparent pixel 914 * at specified location (taking tolerance into account) 915 * @param {CanvasRenderingContext2D} ctx context 916 * @param {Number} x x coordinate 917 * @param {Number} y y coordinate 918 * @param {Number} tolerance Tolerance 919 */ 920 isTransparent: function(ctx, x, y, tolerance) { 921 922 // If tolerance is > 0 adjust start coords to take into account. 923 // If moves off Canvas fix to 0 924 if (tolerance > 0) { 925 if (x > tolerance) { 926 x -= tolerance; 927 } 928 else { 929 x = 0; 930 } 931 if (y > tolerance) { 932 y -= tolerance; 933 } 934 else { 935 y = 0; 936 } 937 } 938 939 var _isTransparent = true, 940 imageData = ctx.getImageData(x, y, (tolerance * 2) || 1, (tolerance * 2) || 1); 941 942 // Split image data - for tolerance > 1, pixelDataSize = 4; 943 for (var i = 3, l = imageData.data.length; i < l; i += 4) { 944 var temp = imageData.data[i]; 945 _isTransparent = temp <= 0; 946 if (_isTransparent === false) { 947 break; // Stop if colour found 948 } 949 } 950 951 imageData = null; 952 953 return _isTransparent; 954 }, 955 956 /** 957 * Parse preserveAspectRatio attribute from element 958 * @param {string} attribute to be parsed 959 * @return {Object} an object containing align and meetOrSlice attribute 960 */ 961 parsePreserveAspectRatioAttribute: function(attribute) { 962 var meetOrSlice = 'meet', alignX = 'Mid', alignY = 'Mid', 963 aspectRatioAttrs = attribute.split(' '), align; 964 965 if (aspectRatioAttrs && aspectRatioAttrs.length) { 966 meetOrSlice = aspectRatioAttrs.pop(); 967 if (meetOrSlice !== 'meet' && meetOrSlice !== 'slice') { 968 align = meetOrSlice; 969 meetOrSlice = 'meet'; 970 } 971 else if (aspectRatioAttrs.length) { 972 align = aspectRatioAttrs.pop(); 973 } 974 } 975 //divide align in alignX and alignY 976 alignX = align !== 'none' ? align.slice(1, 4) : 'none'; 977 alignY = align !== 'none' ? align.slice(5, 8) : 'none'; 978 return { 979 meetOrSlice: meetOrSlice, 980 alignX: alignX, 981 alignY: alignY 982 }; 983 } 984 }; 985 986 })(typeof exports !== 'undefined' ? exports : this); 987 988 989 (function() { 990 991 var arcToSegmentsCache = { }, 992 segmentToBezierCache = { }, 993 boundsOfCurveCache = { }, 994 _join = Array.prototype.join; 995 996 /* Adapted from http://dxr.mozilla.org/mozilla-central/source/content/svg/content/src/nsSVGPathDataParser.cpp 997 * by Andrea Bogazzi code is under MPL. if you don't have a copy of the license you can take it here 998 * http://mozilla.org/MPL/2.0/ 999 */ 1000 function arcToSegments(toX, toY, rx, ry, large, sweep, rotateX) { 1001 var argsString = _join.call(arguments); 1002 if (arcToSegmentsCache[argsString]) { 1003 return arcToSegmentsCache[argsString]; 1004 } 1005 1006 var PI = Math.PI, th = rotateX * PI / 180, 1007 sinTh = Math.sin(th), 1008 cosTh = Math.cos(th), 1009 fromX = 0, fromY = 0; 1010 1011 rx = Math.abs(rx); 1012 ry = Math.abs(ry); 1013 1014 var px = -cosTh * toX * 0.5 - sinTh * toY * 0.5, 1015 py = -cosTh * toY * 0.5 + sinTh * toX * 0.5, 1016 rx2 = rx * rx, ry2 = ry * ry, py2 = py * py, px2 = px * px, 1017 pl = rx2 * ry2 - rx2 * py2 - ry2 * px2, 1018 root = 0; 1019 1020 if (pl < 0) { 1021 var s = Math.sqrt(1 - pl/(rx2 * ry2)); 1022 rx *= s; 1023 ry *= s; 1024 } 1025 else { 1026 root = (large === sweep ? -1.0 : 1.0) * 1027 Math.sqrt( pl /(rx2 * py2 + ry2 * px2)); 1028 } 1029 1030 var cx = root * rx * py / ry, 1031 cy = -root * ry * px / rx, 1032 cx1 = cosTh * cx - sinTh * cy + toX * 0.5, 1033 cy1 = sinTh * cx + cosTh * cy + toY * 0.5, 1034 mTheta = calcVectorAngle(1, 0, (px - cx) / rx, (py - cy) / ry), 1035 dtheta = calcVectorAngle((px - cx) / rx, (py - cy) / ry, (-px - cx) / rx, (-py - cy) / ry); 1036 1037 if (sweep === 0 && dtheta > 0) { 1038 dtheta -= 2 * PI; 1039 } 1040 else if (sweep === 1 && dtheta < 0) { 1041 dtheta += 2 * PI; 1042 } 1043 1044 // Convert into cubic bezier segments <= 90deg 1045 var segments = Math.ceil(Math.abs(dtheta / PI * 2)), 1046 result = [], mDelta = dtheta / segments, 1047 mT = 8 / 3 * Math.sin(mDelta / 4) * Math.sin(mDelta / 4) / Math.sin(mDelta / 2), 1048 th3 = mTheta + mDelta; 1049 1050 for (var i = 0; i < segments; i++) { 1051 result[i] = segmentToBezier(mTheta, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY); 1052 fromX = result[i][4]; 1053 fromY = result[i][5]; 1054 mTheta = th3; 1055 th3 += mDelta; 1056 } 1057 arcToSegmentsCache[argsString] = result; 1058 return result; 1059 } 1060 1061 function segmentToBezier(th2, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY) { 1062 var argsString2 = _join.call(arguments); 1063 if (segmentToBezierCache[argsString2]) { 1064 return segmentToBezierCache[argsString2]; 1065 } 1066 1067 var costh2 = Math.cos(th2), 1068 sinth2 = Math.sin(th2), 1069 costh3 = Math.cos(th3), 1070 sinth3 = Math.sin(th3), 1071 toX = cosTh * rx * costh3 - sinTh * ry * sinth3 + cx1, 1072 toY = sinTh * rx * costh3 + cosTh * ry * sinth3 + cy1, 1073 cp1X = fromX + mT * ( - cosTh * rx * sinth2 - sinTh * ry * costh2), 1074 cp1Y = fromY + mT * ( - sinTh * rx * sinth2 + cosTh * ry * costh2), 1075 cp2X = toX + mT * ( cosTh * rx * sinth3 + sinTh * ry * costh3), 1076 cp2Y = toY + mT * ( sinTh * rx * sinth3 - cosTh * ry * costh3); 1077 1078 segmentToBezierCache[argsString2] = [ 1079 cp1X, cp1Y, 1080 cp2X, cp2Y, 1081 toX, toY 1082 ]; 1083 return segmentToBezierCache[argsString2]; 1084 } 1085 1086 /* 1087 * Private 1088 */ 1089 function calcVectorAngle(ux, uy, vx, vy) { 1090 var ta = Math.atan2(uy, ux), 1091 tb = Math.atan2(vy, vx); 1092 if (tb >= ta) { 1093 return tb - ta; 1094 } 1095 else { 1096 return 2 * Math.PI - (ta - tb); 1097 } 1098 } 1099 1100 /** 1101 * Draws arc 1102 * @param {CanvasRenderingContext2D} ctx 1103 * @param {Number} fx 1104 * @param {Number} fy 1105 * @param {Array} coords 1106 */ 1107 fabric.util.drawArc = function(ctx, fx, fy, coords) { 1108 var rx = coords[0], 1109 ry = coords[1], 1110 rot = coords[2], 1111 large = coords[3], 1112 sweep = coords[4], 1113 tx = coords[5], 1114 ty = coords[6], 1115 segs = [[ ], [ ], [ ], [ ]], 1116 segsNorm = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot); 1117 1118 for (var i = 0, len = segsNorm.length; i < len; i++) { 1119 segs[i][0] = segsNorm[i][0] + fx; 1120 segs[i][1] = segsNorm[i][1] + fy; 1121 segs[i][2] = segsNorm[i][2] + fx; 1122 segs[i][3] = segsNorm[i][3] + fy; 1123 segs[i][4] = segsNorm[i][4] + fx; 1124 segs[i][5] = segsNorm[i][5] + fy; 1125 ctx.bezierCurveTo.apply(ctx, segs[i]); 1126 } 1127 }; 1128 1129 /** 1130 * Calculate bounding box of a elliptic-arc 1131 * @param {Number} fx start point of arc 1132 * @param {Number} fy 1133 * @param {Number} rx horizontal radius 1134 * @param {Number} ry vertical radius 1135 * @param {Number} rot angle of horizontal axe 1136 * @param {Number} large 1 or 0, whatever the arc is the big or the small on the 2 points 1137 * @param {Number} sweep 1 or 0, 1 clockwise or counterclockwise direction 1138 * @param {Number} tx end point of arc 1139 * @param {Number} ty 1140 */ 1141 fabric.util.getBoundsOfArc = function(fx, fy, rx, ry, rot, large, sweep, tx, ty) { 1142 1143 var fromX = 0, fromY = 0, bound = [ ], bounds = [ ], 1144 segs = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot), 1145 boundCopy = [[ ], [ ]]; 1146 1147 for (var i = 0, len = segs.length; i < len; i++) { 1148 bound = getBoundsOfCurve(fromX, fromY, segs[i][0], segs[i][1], segs[i][2], segs[i][3], segs[i][4], segs[i][5]); 1149 boundCopy[0].x = bound[0].x + fx; 1150 boundCopy[0].y = bound[0].y + fy; 1151 boundCopy[1].x = bound[1].x + fx; 1152 boundCopy[1].y = bound[1].y + fy; 1153 bounds.push(boundCopy[0]); 1154 bounds.push(boundCopy[1]); 1155 fromX = segs[i][4]; 1156 fromY = segs[i][5]; 1157 } 1158 return bounds; 1159 }; 1160 1161 /** 1162 * Calculate bounding box of a beziercurve 1163 * @param {Number} x0 starting point 1164 * @param {Number} y0 1165 * @param {Number} x1 first control point 1166 * @param {Number} y1 1167 * @param {Number} x2 secondo control point 1168 * @param {Number} y2 1169 * @param {Number} x3 end of beizer 1170 * @param {Number} y3 1171 */ 1172 // taken from http://jsbin.com/ivomiq/56/edit no credits available for that. 1173 function getBoundsOfCurve(x0, y0, x1, y1, x2, y2, x3, y3) { 1174 var argsString = _join.call(arguments); 1175 if (boundsOfCurveCache[argsString]) { 1176 return boundsOfCurveCache[argsString]; 1177 } 1178 1179 var sqrt = Math.sqrt, 1180 min = Math.min, max = Math.max, 1181 abs = Math.abs, tvalues = [ ], 1182 bounds = [[ ], [ ]], 1183 a, b, c, t, t1, t2, b2ac, sqrtb2ac; 1184 1185 b = 6 * x0 - 12 * x1 + 6 * x2; 1186 a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3; 1187 c = 3 * x1 - 3 * x0; 1188 1189 for (var i = 0; i < 2; ++i) { 1190 if (i > 0) { 1191 b = 6 * y0 - 12 * y1 + 6 * y2; 1192 a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3; 1193 c = 3 * y1 - 3 * y0; 1194 } 1195 1196 if (abs(a) < 1e-12) { 1197 if (abs(b) < 1e-12) { 1198 continue; 1199 } 1200 t = -c / b; 1201 if (0 < t && t < 1) { 1202 tvalues.push(t); 1203 } 1204 continue; 1205 } 1206 b2ac = b * b - 4 * c * a; 1207 if (b2ac < 0) { 1208 continue; 1209 } 1210 sqrtb2ac = sqrt(b2ac); 1211 t1 = (-b + sqrtb2ac) / (2 * a); 1212 if (0 < t1 && t1 < 1) { 1213 tvalues.push(t1); 1214 } 1215 t2 = (-b - sqrtb2ac) / (2 * a); 1216 if (0 < t2 && t2 < 1) { 1217 tvalues.push(t2); 1218 } 1219 } 1220 1221 var x, y, j = tvalues.length, jlen = j, mt; 1222 while (j--) { 1223 t = tvalues[j]; 1224 mt = 1 - t; 1225 x = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3); 1226 bounds[0][j] = x; 1227 1228 y = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3); 1229 bounds[1][j] = y; 1230 } 1231 1232 bounds[0][jlen] = x0; 1233 bounds[1][jlen] = y0; 1234 bounds[0][jlen + 1] = x3; 1235 bounds[1][jlen + 1] = y3; 1236 var result = [ 1237 { 1238 x: min.apply(null, bounds[0]), 1239 y: min.apply(null, bounds[1]) 1240 }, 1241 { 1242 x: max.apply(null, bounds[0]), 1243 y: max.apply(null, bounds[1]) 1244 } 1245 ]; 1246 boundsOfCurveCache[argsString] = result; 1247 return result; 1248 } 1249 1250 fabric.util.getBoundsOfCurve = getBoundsOfCurve; 1251 1252 })(); 1253 1254 1255 (function() { 1256 1257 var slice = Array.prototype.slice; 1258 1259 /* _ES5_COMPAT_START_ */ 1260 1261 if (!Array.prototype.indexOf) { 1262 /** 1263 * Finds index of an element in an array 1264 * @param {Any} searchElement 1265 * @param {Number} [fromIndex] 1266 * @return {Number} 1267 */ 1268 Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) { 1269 if (this === void 0 || this === null) { 1270 throw new TypeError(); 1271 } 1272 var t = Object(this), len = t.length >>> 0; 1273 if (len === 0) { 1274 return -1; 1275 } 1276 var n = 0; 1277 if (arguments.length > 0) { 1278 n = Number(arguments[1]); 1279 if (n !== n) { // shortcut for verifying if it's NaN 1280 n = 0; 1281 } 1282 else if (n !== 0 && n !== Number.POSITIVE_INFINITY && n !== Number.NEGATIVE_INFINITY) { 1283 n = (n > 0 || -1) * Math.floor(Math.abs(n)); 1284 } 1285 } 1286 if (n >= len) { 1287 return -1; 1288 } 1289 var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); 1290 for (; k < len; k++) { 1291 if (k in t && t[k] === searchElement) { 1292 return k; 1293 } 1294 } 1295 return -1; 1296 }; 1297 } 1298 1299 if (!Array.prototype.forEach) { 1300 /** 1301 * Iterates an array, invoking callback for each element 1302 * @param {Function} fn Callback to invoke for each element 1303 * @param {Object} [context] Context to invoke callback in 1304 * @return {Array} 1305 */ 1306 Array.prototype.forEach = function(fn, context) { 1307 for (var i = 0, len = this.length >>> 0; i < len; i++) { 1308 if (i in this) { 1309 fn.call(context, this[i], i, this); 1310 } 1311 } 1312 }; 1313 } 1314 1315 if (!Array.prototype.map) { 1316 /** 1317 * Returns a result of iterating over an array, invoking callback for each element 1318 * @param {Function} fn Callback to invoke for each element 1319 * @param {Object} [context] Context to invoke callback in 1320 * @return {Array} 1321 */ 1322 Array.prototype.map = function(fn, context) { 1323 var result = [ ]; 1324 for (var i = 0, len = this.length >>> 0; i < len; i++) { 1325 if (i in this) { 1326 result[i] = fn.call(context, this[i], i, this); 1327 } 1328 } 1329 return result; 1330 }; 1331 } 1332 1333 if (!Array.prototype.every) { 1334 /** 1335 * Returns true if a callback returns truthy value for all elements in an array 1336 * @param {Function} fn Callback to invoke for each element 1337 * @param {Object} [context] Context to invoke callback in 1338 * @return {Boolean} 1339 */ 1340 Array.prototype.every = function(fn, context) { 1341 for (var i = 0, len = this.length >>> 0; i < len; i++) { 1342 if (i in this && !fn.call(context, this[i], i, this)) { 1343 return false; 1344 } 1345 } 1346 return true; 1347 }; 1348 } 1349 1350 if (!Array.prototype.some) { 1351 /** 1352 * Returns true if a callback returns truthy value for at least one element in an array 1353 * @param {Function} fn Callback to invoke for each element 1354 * @param {Object} [context] Context to invoke callback in 1355 * @return {Boolean} 1356 */ 1357 Array.prototype.some = function(fn, context) { 1358 for (var i = 0, len = this.length >>> 0; i < len; i++) { 1359 if (i in this && fn.call(context, this[i], i, this)) { 1360 return true; 1361 } 1362 } 1363 return false; 1364 }; 1365 } 1366 1367 if (!Array.prototype.filter) { 1368 /** 1369 * Returns the result of iterating over elements in an array 1370 * @param {Function} fn Callback to invoke for each element 1371 * @param {Object} [context] Context to invoke callback in 1372 * @return {Array} 1373 */ 1374 Array.prototype.filter = function(fn, context) { 1375 var result = [ ], val; 1376 for (var i = 0, len = this.length >>> 0; i < len; i++) { 1377 if (i in this) { 1378 val = this[i]; // in case fn mutates this 1379 if (fn.call(context, val, i, this)) { 1380 result.push(val); 1381 } 1382 } 1383 } 1384 return result; 1385 }; 1386 } 1387 1388 if (!Array.prototype.reduce) { 1389 /** 1390 * Returns "folded" (reduced) result of iterating over elements in an array 1391 * @param {Function} fn Callback to invoke for each element 1392 * @param {Object} [initial] Object to use as the first argument to the first call of the callback 1393 * @return {Any} 1394 */ 1395 Array.prototype.reduce = function(fn /*, initial*/) { 1396 var len = this.length >>> 0, 1397 i = 0, 1398 rv; 1399 1400 if (arguments.length > 1) { 1401 rv = arguments[1]; 1402 } 1403 else { 1404 do { 1405 if (i in this) { 1406 rv = this[i++]; 1407 break; 1408 } 1409 // if array contains no values, no initial value to return 1410 if (++i >= len) { 1411 throw new TypeError(); 1412 } 1413 } 1414 while (true); 1415 } 1416 for (; i < len; i++) { 1417 if (i in this) { 1418 rv = fn.call(null, rv, this[i], i, this); 1419 } 1420 } 1421 return rv; 1422 }; 1423 } 1424 1425 /* _ES5_COMPAT_END_ */ 1426 1427 /** 1428 * Invokes method on all items in a given array 1429 * @memberOf fabric.util.array 1430 * @param {Array} array Array to iterate over 1431 * @param {String} method Name of a method to invoke 1432 * @return {Array} 1433 */ 1434 function invoke(array, method) { 1435 var args = slice.call(arguments, 2), result = [ ]; 1436 for (var i = 0, len = array.length; i < len; i++) { 1437 result[i] = args.length ? array[i][method].apply(array[i], args) : array[i][method].call(array[i]); 1438 } 1439 return result; 1440 } 1441 1442 /** 1443 * Finds maximum value in array (not necessarily "first" one) 1444 * @memberOf fabric.util.array 1445 * @param {Array} array Array to iterate over 1446 * @param {String} byProperty 1447 * @return {Any} 1448 */ 1449 function max(array, byProperty) { 1450 return find(array, byProperty, function(value1, value2) { 1451 return value1 >= value2; 1452 }); 1453 } 1454 1455 /** 1456 * Finds minimum value in array (not necessarily "first" one) 1457 * @memberOf fabric.util.array 1458 * @param {Array} array Array to iterate over 1459 * @param {String} byProperty 1460 * @return {Any} 1461 */ 1462 function min(array, byProperty) { 1463 return find(array, byProperty, function(value1, value2) { 1464 return value1 < value2; 1465 }); 1466 } 1467 1468 /** 1469 * @private 1470 */ 1471 function find(array, byProperty, condition) { 1472 if (!array || array.length === 0) { 1473 return; 1474 } 1475 1476 var i = array.length - 1, 1477 result = byProperty ? array[i][byProperty] : array[i]; 1478 if (byProperty) { 1479 while (i--) { 1480 if (condition(array[i][byProperty], result)) { 1481 result = array[i][byProperty]; 1482 } 1483 } 1484 } 1485 else { 1486 while (i--) { 1487 if (condition(array[i], result)) { 1488 result = array[i]; 1489 } 1490 } 1491 } 1492 return result; 1493 } 1494 1495 /** 1496 * @namespace fabric.util.array 1497 */ 1498 fabric.util.array = { 1499 invoke: invoke, 1500 min: min, 1501 max: max 1502 }; 1503 1504 })(); 1505 1506 1507 (function() { 1508 1509 /** 1510 * Copies all enumerable properties of one object to another 1511 * @memberOf fabric.util.object 1512 * @param {Object} destination Where to copy to 1513 * @param {Object} source Where to copy from 1514 * @return {Object} 1515 */ 1516 function extend(destination, source) { 1517 // JScript DontEnum bug is not taken care of 1518 for (var property in source) { 1519 destination[property] = source[property]; 1520 } 1521 return destination; 1522 } 1523 1524 /** 1525 * Creates an empty object and copies all enumerable properties of another object to it 1526 * @memberOf fabric.util.object 1527 * @param {Object} object Object to clone 1528 * @return {Object} 1529 */ 1530 function clone(object) { 1531 return extend({ }, object); 1532 } 1533 1534 /** @namespace fabric.util.object */ 1535 fabric.util.object = { 1536 extend: extend, 1537 clone: clone 1538 }; 1539 1540 })(); 1541 1542 1543 (function() { 1544 1545 /* _ES5_COMPAT_START_ */ 1546 if (!String.prototype.trim) { 1547 /** 1548 * Trims a string (removing whitespace from the beginning and the end) 1549 * @function external:String#trim 1550 * @see <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/Trim">String#trim on MDN</a> 1551 */ 1552 String.prototype.trim = function () { 1553 // this trim is not fully ES3 or ES5 compliant, but it should cover most cases for now 1554 return this.replace(/^[\s\xA0]+/, '').replace(/[\s\xA0]+$/, ''); 1555 }; 1556 } 1557 /* _ES5_COMPAT_END_ */ 1558 1559 /** 1560 * Camelizes a string 1561 * @memberOf fabric.util.string 1562 * @param {String} string String to camelize 1563 * @return {String} Camelized version of a string 1564 */ 1565 function camelize(string) { 1566 return string.replace(/-+(.)?/g, function(match, character) { 1567 return character ? character.toUpperCase() : ''; 1568 }); 1569 } 1570 1571 /** 1572 * Capitalizes a string 1573 * @memberOf fabric.util.string 1574 * @param {String} string String to capitalize 1575 * @param {Boolean} [firstLetterOnly] If true only first letter is capitalized 1576 * and other letters stay untouched, if false first letter is capitalized 1577 * and other letters are converted to lowercase. 1578 * @return {String} Capitalized version of a string 1579 */ 1580 function capitalize(string, firstLetterOnly) { 1581 return string.charAt(0).toUpperCase() + 1582 (firstLetterOnly ? string.slice(1) : string.slice(1).toLowerCase()); 1583 } 1584 1585 /** 1586 * Escapes XML in a string 1587 * @memberOf fabric.util.string 1588 * @param {String} string String to escape 1589 * @return {String} Escaped version of a string 1590 */ 1591 function escapeXml(string) { 1592 return string.replace(/&/g, '&') 1593 .replace(/"/g, '"') 1594 .replace(/'/g, ''') 1595 .replace(/</g, '<') 1596 .replace(/>/g, '>'); 1597 } 1598 1599 /** 1600 * String utilities 1601 * @namespace fabric.util.string 1602 */ 1603 fabric.util.string = { 1604 camelize: camelize, 1605 capitalize: capitalize, 1606 escapeXml: escapeXml 1607 }; 1608 }()); 1609 1610 1611 /* _ES5_COMPAT_START_ */ 1612 (function() { 1613 1614 var slice = Array.prototype.slice, 1615 apply = Function.prototype.apply, 1616 Dummy = function() { }; 1617 1618 if (!Function.prototype.bind) { 1619 /** 1620 * Cross-browser approximation of ES5 Function.prototype.bind (not fully spec conforming) 1621 * @see <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind">Function#bind on MDN</a> 1622 * @param {Object} thisArg Object to bind function to 1623 * @param {Any[]} Values to pass to a bound function 1624 * @return {Function} 1625 */ 1626 Function.prototype.bind = function(thisArg) { 1627 var _this = this, args = slice.call(arguments, 1), bound; 1628 if (args.length) { 1629 bound = function() { 1630 return apply.call(_this, this instanceof Dummy ? this : thisArg, args.concat(slice.call(arguments))); 1631 }; 1632 } 1633 else { 1634 /** @ignore */ 1635 bound = function() { 1636 return apply.call(_this, this instanceof Dummy ? this : thisArg, arguments); 1637 }; 1638 } 1639 Dummy.prototype = this.prototype; 1640 bound.prototype = new Dummy(); 1641 1642 return bound; 1643 }; 1644 } 1645 1646 })(); 1647 /* _ES5_COMPAT_END_ */ 1648 1649 1650 (function() { 1651 1652 var slice = Array.prototype.slice, emptyFunction = function() { }, 1653 1654 IS_DONTENUM_BUGGY = (function() { 1655 for (var p in { toString: 1 }) { 1656 if (p === 'toString') { 1657 return false; 1658 } 1659 } 1660 return true; 1661 })(), 1662 1663 /** @ignore */ 1664 addMethods = function(klass, source, parent) { 1665 for (var property in source) { 1666 1667 if (property in klass.prototype && 1668 typeof klass.prototype[property] === 'function' && 1669 (source[property] + '').indexOf('callSuper') > -1) { 1670 1671 klass.prototype[property] = (function(property) { 1672 return function() { 1673 1674 var superclass = this.constructor.superclass; 1675 this.constructor.superclass = parent; 1676 var returnValue = source[property].apply(this, arguments); 1677 this.constructor.superclass = superclass; 1678 1679 if (property !== 'initialize') { 1680 return returnValue; 1681 } 1682 }; 1683 })(property); 1684 } 1685 else { 1686 klass.prototype[property] = source[property]; 1687 } 1688 1689 if (IS_DONTENUM_BUGGY) { 1690 if (source.toString !== Object.prototype.toString) { 1691 klass.prototype.toString = source.toString; 1692 } 1693 if (source.valueOf !== Object.prototype.valueOf) { 1694 klass.prototype.valueOf = source.valueOf; 1695 } 1696 } 1697 } 1698 }; 1699 1700 function Subclass() { } 1701 1702 function callSuper(methodName) { 1703 var fn = this.constructor.superclass.prototype[methodName]; 1704 return (arguments.length > 1) 1705 ? fn.apply(this, slice.call(arguments, 1)) 1706 : fn.call(this); 1707 } 1708 1709 /** 1710 * Helper for creation of "classes". 1711 * @memberOf fabric.util 1712 * @param {Function} [parent] optional "Class" to inherit from 1713 * @param {Object} [properties] Properties shared by all instances of this class 1714 * (be careful modifying objects defined here as this would affect all instances) 1715 */ 1716 function createClass() { 1717 var parent = null, 1718 properties = slice.call(arguments, 0); 1719 1720 if (typeof properties[0] === 'function') { 1721 parent = properties.shift(); 1722 } 1723 function klass() { 1724 this.initialize.apply(this, arguments); 1725 } 1726 1727 klass.superclass = parent; 1728 klass.subclasses = [ ]; 1729 1730 if (parent) { 1731 Subclass.prototype = parent.prototype; 1732 klass.prototype = new Subclass(); 1733 parent.subclasses.push(klass); 1734 } 1735 for (var i = 0, length = properties.length; i < length; i++) { 1736 addMethods(klass, properties[i], parent); 1737 } 1738 if (!klass.prototype.initialize) { 1739 klass.prototype.initialize = emptyFunction; 1740 } 1741 klass.prototype.constructor = klass; 1742 klass.prototype.callSuper = callSuper; 1743 return klass; 1744 } 1745 1746 fabric.util.createClass = createClass; 1747 })(); 1748 1749 1750 (function () { 1751 1752 var unknown = 'unknown'; 1753 1754 /* EVENT HANDLING */ 1755 1756 function areHostMethods(object) { 1757 var methodNames = Array.prototype.slice.call(arguments, 1), 1758 t, i, len = methodNames.length; 1759 for (i = 0; i < len; i++) { 1760 t = typeof object[methodNames[i]]; 1761 if (!(/^(?:function|object|unknown)$/).test(t)) { 1762 return false; 1763 } 1764 } 1765 return true; 1766 } 1767 1768 /** @ignore */ 1769 var getElement, 1770 setElement, 1771 getUniqueId = (function () { 1772 var uid = 0; 1773 return function (element) { 1774 return element.__uniqueID || (element.__uniqueID = 'uniqueID__' + uid++); 1775 }; 1776 })(); 1777 1778 (function () { 1779 var elements = { }; 1780 /** @ignore */ 1781 getElement = function (uid) { 1782 return elements[uid]; 1783 }; 1784 /** @ignore */ 1785 setElement = function (uid, element) { 1786 elements[uid] = element; 1787 }; 1788 })(); 1789 1790 function createListener(uid, handler) { 1791 return { 1792 handler: handler, 1793 wrappedHandler: createWrappedHandler(uid, handler) 1794 }; 1795 } 1796 1797 function createWrappedHandler(uid, handler) { 1798 return function (e) { 1799 handler.call(getElement(uid), e || fabric.window.event); 1800 }; 1801 } 1802 1803 function createDispatcher(uid, eventName) { 1804 return function (e) { 1805 if (handlers[uid] && handlers[uid][eventName]) { 1806 var handlersForEvent = handlers[uid][eventName]; 1807 for (var i = 0, len = handlersForEvent.length; i < len; i++) { 1808 handlersForEvent[i].call(this, e || fabric.window.event); 1809 } 1810 } 1811 }; 1812 } 1813 1814 var shouldUseAddListenerRemoveListener = ( 1815 areHostMethods(fabric.document.documentElement, 'addEventListener', 'removeEventListener') && 1816 areHostMethods(fabric.window, 'addEventListener', 'removeEventListener')), 1817 1818 shouldUseAttachEventDetachEvent = ( 1819 areHostMethods(fabric.document.documentElement, 'attachEvent', 'detachEvent') && 1820 areHostMethods(fabric.window, 'attachEvent', 'detachEvent')), 1821 1822 // IE branch 1823 listeners = { }, 1824 1825 // DOM L0 branch 1826 handlers = { }, 1827 1828 addListener, removeListener; 1829 1830 if (shouldUseAddListenerRemoveListener) { 1831 /** @ignore */ 1832 addListener = function (element, eventName, handler) { 1833 element.addEventListener(eventName, handler, false); 1834 }; 1835 /** @ignore */ 1836 removeListener = function (element, eventName, handler) { 1837 element.removeEventListener(eventName, handler, false); 1838 }; 1839 } 1840 1841 else if (shouldUseAttachEventDetachEvent) { 1842 /** @ignore */ 1843 addListener = function (element, eventName, handler) { 1844 var uid = getUniqueId(element); 1845 setElement(uid, element); 1846 if (!listeners[uid]) { 1847 listeners[uid] = { }; 1848 } 1849 if (!listeners[uid][eventName]) { 1850 listeners[uid][eventName] = [ ]; 1851 1852 } 1853 var listener = createListener(uid, handler); 1854 listeners[uid][eventName].push(listener); 1855 element.attachEvent('on' + eventName, listener.wrappedHandler); 1856 }; 1857 /** @ignore */ 1858 removeListener = function (element, eventName, handler) { 1859 var uid = getUniqueId(element), listener; 1860 if (listeners[uid] && listeners[uid][eventName]) { 1861 for (var i = 0, len = listeners[uid][eventName].length; i < len; i++) { 1862 listener = listeners[uid][eventName][i]; 1863 if (listener && listener.handler === handler) { 1864 element.detachEvent('on' + eventName, listener.wrappedHandler); 1865 listeners[uid][eventName][i] = null; 1866 } 1867 } 1868 } 1869 }; 1870 } 1871 else { 1872 /** @ignore */ 1873 addListener = function (element, eventName, handler) { 1874 var uid = getUniqueId(element); 1875 if (!handlers[uid]) { 1876 handlers[uid] = { }; 1877 } 1878 if (!handlers[uid][eventName]) { 1879 handlers[uid][eventName] = [ ]; 1880 var existingHandler = element['on' + eventName]; 1881 if (existingHandler) { 1882 handlers[uid][eventName].push(existingHandler); 1883 } 1884 element['on' + eventName] = createDispatcher(uid, eventName); 1885 } 1886 handlers[uid][eventName].push(handler); 1887 }; 1888 /** @ignore */ 1889 removeListener = function (element, eventName, handler) { 1890 var uid = getUniqueId(element); 1891 if (handlers[uid] && handlers[uid][eventName]) { 1892 var handlersForEvent = handlers[uid][eventName]; 1893 for (var i = 0, len = handlersForEvent.length; i < len; i++) { 1894 if (handlersForEvent[i] === handler) { 1895 handlersForEvent.splice(i, 1); 1896 } 1897 } 1898 } 1899 }; 1900 } 1901 1902 /** 1903 * Adds an event listener to an element 1904 * @function 1905 * @memberOf fabric.util 1906 * @param {HTMLElement} element 1907 * @param {String} eventName 1908 * @param {Function} handler 1909 */ 1910 fabric.util.addListener = addListener; 1911 1912 /** 1913 * Removes an event listener from an element 1914 * @function 1915 * @memberOf fabric.util 1916 * @param {HTMLElement} element 1917 * @param {String} eventName 1918 * @param {Function} handler 1919 */ 1920 fabric.util.removeListener = removeListener; 1921 1922 /** 1923 * Cross-browser wrapper for getting event's coordinates 1924 * @memberOf fabric.util 1925 * @param {Event} event Event object 1926 */ 1927 function getPointer(event) { 1928 event || (event = fabric.window.event); 1929 1930 var element = event.target || 1931 (typeof event.srcElement !== unknown ? event.srcElement : null), 1932 1933 scroll = fabric.util.getScrollLeftTop(element); 1934 1935 return { 1936 x: pointerX(event) + scroll.left, 1937 y: pointerY(event) + scroll.top 1938 }; 1939 } 1940 1941 var pointerX = function(event) { 1942 // looks like in IE (<9) clientX at certain point (apparently when mouseup fires on VML element) 1943 // is represented as COM object, with all the consequences, like "unknown" type and error on [[Get]] 1944 // need to investigate later 1945 return (typeof event.clientX !== unknown ? event.clientX : 0); 1946 }, 1947 1948 pointerY = function(event) { 1949 return (typeof event.clientY !== unknown ? event.clientY : 0); 1950 }; 1951 1952 function _getPointer(event, pageProp, clientProp) { 1953 var touchProp = event.type === 'touchend' ? 'changedTouches' : 'touches'; 1954 1955 return (event[touchProp] && event[touchProp][0] 1956 ? (event[touchProp][0][pageProp] - (event[touchProp][0][pageProp] - event[touchProp][0][clientProp])) 1957 || event[clientProp] 1958 : event[clientProp]); 1959 } 1960 1961 if (fabric.isTouchSupported) { 1962 pointerX = function(event) { 1963 return _getPointer(event, 'pageX', 'clientX'); 1964 }; 1965 pointerY = function(event) { 1966 return _getPointer(event, 'pageY', 'clientY'); 1967 }; 1968 } 1969 1970 fabric.util.getPointer = getPointer; 1971 1972 fabric.util.object.extend(fabric.util, fabric.Observable); 1973 1974 })(); 1975 1976 1977 (function () { 1978 1979 /** 1980 * Cross-browser wrapper for setting element's style 1981 * @memberOf fabric.util 1982 * @param {HTMLElement} element 1983 * @param {Object} styles 1984 * @return {HTMLElement} Element that was passed as a first argument 1985 */ 1986 function setStyle(element, styles) { 1987 var elementStyle = element.style; 1988 if (!elementStyle) { 1989 return element; 1990 } 1991 if (typeof styles === 'string') { 1992 element.style.cssText += ';' + styles; 1993 return styles.indexOf('opacity') > -1 1994 ? setOpacity(element, styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) 1995 : element; 1996 } 1997 for (var property in styles) { 1998 if (property === 'opacity') { 1999 setOpacity(element, styles[property]); 2000 } 2001 else { 2002 var normalizedProperty = (property === 'float' || property === 'cssFloat') 2003 ? (typeof elementStyle.styleFloat === 'undefined' ? 'cssFloat' : 'styleFloat') 2004 : property; 2005 elementStyle[normalizedProperty] = styles[property]; 2006 } 2007 } 2008 return element; 2009 } 2010 2011 var parseEl = fabric.document.createElement('div'), 2012 supportsOpacity = typeof parseEl.style.opacity === 'string', 2013 supportsFilters = typeof parseEl.style.filter === 'string', 2014 reOpacity = /alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/, 2015 2016 /** @ignore */ 2017 setOpacity = function (element) { return element; }; 2018 2019 if (supportsOpacity) { 2020 /** @ignore */ 2021 setOpacity = function(element, value) { 2022 element.style.opacity = value; 2023 return element; 2024 }; 2025 } 2026 else if (supportsFilters) { 2027 /** @ignore */ 2028 setOpacity = function(element, value) { 2029 var es = element.style; 2030 if (element.currentStyle && !element.currentStyle.hasLayout) { 2031 es.zoom = 1; 2032 } 2033 if (reOpacity.test(es.filter)) { 2034 value = value >= 0.9999 ? '' : ('alpha(opacity=' + (value * 100) + ')'); 2035 es.filter = es.filter.replace(reOpacity, value); 2036 } 2037 else { 2038 es.filter += ' alpha(opacity=' + (value * 100) + ')'; 2039 } 2040 return element; 2041 }; 2042 } 2043 2044 fabric.util.setStyle = setStyle; 2045 2046 })(); 2047 2048 2049 (function() { 2050 2051 var _slice = Array.prototype.slice; 2052 2053 /** 2054 * Takes id and returns an element with that id (if one exists in a document) 2055 * @memberOf fabric.util 2056 * @param {String|HTMLElement} id 2057 * @return {HTMLElement|null} 2058 */ 2059 function getById(id) { 2060 return typeof id === 'string' ? fabric.document.getElementById(id) : id; 2061 } 2062 2063 var sliceCanConvertNodelists, 2064 /** 2065 * Converts an array-like object (e.g. arguments or NodeList) to an array 2066 * @memberOf fabric.util 2067 * @param {Object} arrayLike 2068 * @return {Array} 2069 */ 2070 toArray = function(arrayLike) { 2071 return _slice.call(arrayLike, 0); 2072 }; 2073 2074 try { 2075 sliceCanConvertNodelists = toArray(fabric.document.childNodes) instanceof Array; 2076 } 2077 catch (err) { } 2078 2079 if (!sliceCanConvertNodelists) { 2080 toArray = function(arrayLike) { 2081 var arr = new Array(arrayLike.length), i = arrayLike.length; 2082 while (i--) { 2083 arr[i] = arrayLike[i]; 2084 } 2085 return arr; 2086 }; 2087 } 2088 2089 /** 2090 * Creates specified element with specified attributes 2091 * @memberOf fabric.util 2092 * @param {String} tagName Type of an element to create 2093 * @param {Object} [attributes] Attributes to set on an element 2094 * @return {HTMLElement} Newly created element 2095 */ 2096 function makeElement(tagName, attributes) { 2097 var el = fabric.document.createElement(tagName); 2098 for (var prop in attributes) { 2099 if (prop === 'class') { 2100 el.className = attributes[prop]; 2101 } 2102 else if (prop === 'for') { 2103 el.htmlFor = attributes[prop]; 2104 } 2105 else { 2106 el.setAttribute(prop, attributes[prop]); 2107 } 2108 } 2109 return el; 2110 } 2111 2112 /** 2113 * Adds class to an element 2114 * @memberOf fabric.util 2115 * @param {HTMLElement} element Element to add class to 2116 * @param {String} className Class to add to an element 2117 */ 2118 function addClass(element, className) { 2119 if (element && (' ' + element.className + ' ').indexOf(' ' + className + ' ') === -1) { 2120 element.className += (element.className ? ' ' : '') + className; 2121 } 2122 } 2123 2124 /** 2125 * Wraps element with another element 2126 * @memberOf fabric.util 2127 * @param {HTMLElement} element Element to wrap 2128 * @param {HTMLElement|String} wrapper Element to wrap with 2129 * @param {Object} [attributes] Attributes to set on a wrapper 2130 * @return {HTMLElement} wrapper 2131 */ 2132 function wrapElement(element, wrapper, attributes) { 2133 if (typeof wrapper === 'string') { 2134 wrapper = makeElement(wrapper, attributes); 2135 } 2136 if (element.parentNode) { 2137 element.parentNode.replaceChild(wrapper, element); 2138 } 2139 wrapper.appendChild(element); 2140 return wrapper; 2141 } 2142 2143 /** 2144 * Returns element scroll offsets 2145 * @memberOf fabric.util 2146 * @param {HTMLElement} element Element to operate on 2147 * @return {Object} Object with left/top values 2148 */ 2149 function getScrollLeftTop(element) { 2150 2151 var left = 0, 2152 top = 0, 2153 docElement = fabric.document.documentElement, 2154 body = fabric.document.body || { 2155 scrollLeft: 0, scrollTop: 0 2156 }; 2157 2158 // While loop checks (and then sets element to) .parentNode OR .host 2159 // to account for ShadowDOM. We still want to traverse up out of ShadowDOM, 2160 // but the .parentNode of a root ShadowDOM node will always be null, instead 2161 // it should be accessed through .host. See http://stackoverflow.com/a/24765528/4383938 2162 while (element && (element.parentNode || element.host)) { 2163 2164 // Set element to element parent, or 'host' in case of ShadowDOM 2165 element = element.parentNode || element.host; 2166 2167 if (element === fabric.document) { 2168 left = body.scrollLeft || docElement.scrollLeft || 0; 2169 top = body.scrollTop || docElement.scrollTop || 0; 2170 } 2171 else { 2172 left += element.scrollLeft || 0; 2173 top += element.scrollTop || 0; 2174 } 2175 2176 if (element.nodeType === 1 && 2177 fabric.util.getElementStyle(element, 'position') === 'fixed') { 2178 break; 2179 } 2180 } 2181 2182 return { left: left, top: top }; 2183 } 2184 2185 /** 2186 * Returns offset for a given element 2187 * @function 2188 * @memberOf fabric.util 2189 * @param {HTMLElement} element Element to get offset for 2190 * @return {Object} Object with "left" and "top" properties 2191 */ 2192 function getElementOffset(element) { 2193 var docElem, 2194 doc = element && element.ownerDocument, 2195 box = { left: 0, top: 0 }, 2196 offset = { left: 0, top: 0 }, 2197 scrollLeftTop, 2198 offsetAttributes = { 2199 borderLeftWidth: 'left', 2200 borderTopWidth: 'top', 2201 paddingLeft: 'left', 2202 paddingTop: 'top' 2203 }; 2204 2205 if (!doc) { 2206 return offset; 2207 } 2208 2209 for (var attr in offsetAttributes) { 2210 offset[offsetAttributes[attr]] += parseInt(getElementStyle(element, attr), 10) || 0; 2211 } 2212 2213 docElem = doc.documentElement; 2214 if ( typeof element.getBoundingClientRect !== 'undefined' ) { 2215 box = element.getBoundingClientRect(); 2216 } 2217 2218 scrollLeftTop = getScrollLeftTop(element); 2219 2220 return { 2221 left: box.left + scrollLeftTop.left - (docElem.clientLeft || 0) + offset.left, 2222 top: box.top + scrollLeftTop.top - (docElem.clientTop || 0) + offset.top 2223 }; 2224 } 2225 2226 /** 2227 * Returns style attribute value of a given element 2228 * @memberOf fabric.util 2229 * @param {HTMLElement} element Element to get style attribute for 2230 * @param {String} attr Style attribute to get for element 2231 * @return {String} Style attribute value of the given element. 2232 */ 2233 var getElementStyle; 2234 if (fabric.document.defaultView && fabric.document.defaultView.getComputedStyle) { 2235 getElementStyle = function(element, attr) { 2236 var style = fabric.document.defaultView.getComputedStyle(element, null); 2237 return style ? style[attr] : undefined; 2238 }; 2239 } 2240 else { 2241 getElementStyle = function(element, attr) { 2242 var value = element.style[attr]; 2243 if (!value && element.currentStyle) { 2244 value = element.currentStyle[attr]; 2245 } 2246 return value; 2247 }; 2248 } 2249 2250 (function () { 2251 var style = fabric.document.documentElement.style, 2252 selectProp = 'userSelect' in style 2253 ? 'userSelect' 2254 : 'MozUserSelect' in style 2255 ? 'MozUserSelect' 2256 : 'WebkitUserSelect' in style 2257 ? 'WebkitUserSelect' 2258 : 'KhtmlUserSelect' in style 2259 ? 'KhtmlUserSelect' 2260 : ''; 2261 2262 /** 2263 * Makes element unselectable 2264 * @memberOf fabric.util 2265 * @param {HTMLElement} element Element to make unselectable 2266 * @return {HTMLElement} Element that was passed in 2267 */ 2268 function makeElementUnselectable(element) { 2269 if (typeof element.onselectstart !== 'undefined') { 2270 element.onselectstart = fabric.util.falseFunction; 2271 } 2272 if (selectProp) { 2273 element.style[selectProp] = 'none'; 2274 } 2275 else if (typeof element.unselectable === 'string') { 2276 element.unselectable = 'on'; 2277 } 2278 return element; 2279 } 2280 2281 /** 2282 * Makes element selectable 2283 * @memberOf fabric.util 2284 * @param {HTMLElement} element Element to make selectable 2285 * @return {HTMLElement} Element that was passed in 2286 */ 2287 function makeElementSelectable(element) { 2288 if (typeof element.onselectstart !== 'undefined') { 2289 element.onselectstart = null; 2290 } 2291 if (selectProp) { 2292 element.style[selectProp] = ''; 2293 } 2294 else if (typeof element.unselectable === 'string') { 2295 element.unselectable = ''; 2296 } 2297 return element; 2298 } 2299 2300 fabric.util.makeElementUnselectable = makeElementUnselectable; 2301 fabric.util.makeElementSelectable = makeElementSelectable; 2302 })(); 2303 2304 (function() { 2305 2306 /** 2307 * Inserts a script element with a given url into a document; invokes callback, when that script is finished loading 2308 * @memberOf fabric.util 2309 * @param {String} url URL of a script to load 2310 * @param {Function} callback Callback to execute when script is finished loading 2311 */ 2312 function getScript(url, callback) { 2313 var headEl = fabric.document.getElementsByTagName('head')[0], 2314 scriptEl = fabric.document.createElement('script'), 2315 loading = true; 2316 2317 /** @ignore */ 2318 scriptEl.onload = /** @ignore */ scriptEl.onreadystatechange = function(e) { 2319 if (loading) { 2320 if (typeof this.readyState === 'string' && 2321 this.readyState !== 'loaded' && 2322 this.readyState !== 'complete') { 2323 return; 2324 } 2325 loading = false; 2326 callback(e || fabric.window.event); 2327 scriptEl = scriptEl.onload = scriptEl.onreadystatechange = null; 2328 } 2329 }; 2330 scriptEl.src = url; 2331 headEl.appendChild(scriptEl); 2332 // causes issue in Opera 2333 // headEl.removeChild(scriptEl); 2334 } 2335 2336 fabric.util.getScript = getScript; 2337 })(); 2338 2339 fabric.util.getById = getById; 2340 fabric.util.toArray = toArray; 2341 fabric.util.makeElement = makeElement; 2342 fabric.util.addClass = addClass; 2343 fabric.util.wrapElement = wrapElement; 2344 fabric.util.getScrollLeftTop = getScrollLeftTop; 2345 fabric.util.getElementOffset = getElementOffset; 2346 fabric.util.getElementStyle = getElementStyle; 2347 2348 })(); 2349 2350 2351 (function() { 2352 2353 function addParamToUrl(url, param) { 2354 return url + (/\?/.test(url) ? '&' : '?') + param; 2355 } 2356 2357 var makeXHR = (function() { 2358 var factories = [ 2359 function() { return new ActiveXObject('Microsoft.XMLHTTP'); }, 2360 function() { return new ActiveXObject('Msxml2.XMLHTTP'); }, 2361 function() { return new ActiveXObject('Msxml2.XMLHTTP.3.0'); }, 2362 function() { return new XMLHttpRequest(); } 2363 ]; 2364 for (var i = factories.length; i--; ) { 2365 try { 2366 var req = factories[i](); 2367 if (req) { 2368 return factories[i]; 2369 } 2370 } 2371 catch (err) { } 2372 } 2373 })(); 2374 2375 function emptyFn() { } 2376 2377 /** 2378 * Cross-browser abstraction for sending XMLHttpRequest 2379 * @memberOf fabric.util 2380 * @param {String} url URL to send XMLHttpRequest to 2381 * @param {Object} [options] Options object 2382 * @param {String} [options.method="GET"] 2383 * @param {Function} options.onComplete Callback to invoke when request is completed 2384 * @return {XMLHttpRequest} request 2385 */ 2386 function request(url, options) { 2387 2388 options || (options = { }); 2389 2390 var method = options.method ? options.method.toUpperCase() : 'GET', 2391 onComplete = options.onComplete || function() { }, 2392 xhr = makeXHR(), 2393 body; 2394 2395 /** @ignore */ 2396 xhr.onreadystatechange = function() { 2397 if (xhr.readyState === 4) { 2398 onComplete(xhr); 2399 xhr.onreadystatechange = emptyFn; 2400 } 2401 }; 2402 2403 if (method === 'GET') { 2404 body = null; 2405 if (typeof options.parameters === 'string') { 2406 url = addParamToUrl(url, options.parameters); 2407 } 2408 } 2409 2410 xhr.open(method, url, true); 2411 2412 if (method === 'POST' || method === 'PUT') { 2413 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 2414 } 2415 2416 xhr.send(body); 2417 return xhr; 2418 } 2419 2420 fabric.util.request = request; 2421 })(); 2422 2423 2424 /** 2425 * Wrapper around `console.log` (when available) 2426 * @param {Any} [values] Values to log 2427 */ 2428 fabric.log = function() { }; 2429 2430 /** 2431 * Wrapper around `console.warn` (when available) 2432 * @param {Any} [values] Values to log as a warning 2433 */ 2434 fabric.warn = function() { }; 2435 2436 /* jshint ignore:start */ 2437 if (typeof console !== 'undefined') { 2438 2439 ['log', 'warn'].forEach(function(methodName) { 2440 2441 if (typeof console[methodName] !== 'undefined' && 2442 typeof console[methodName].apply === 'function') { 2443 2444 fabric[methodName] = function() { 2445 return console[methodName].apply(console, arguments); 2446 }; 2447 } 2448 }); 2449 } 2450 /* jshint ignore:end */ 2451 2452 2453 (function() { 2454 2455 /** 2456 * Changes value from one to another within certain period of time, invoking callbacks as value is being changed. 2457 * @memberOf fabric.util 2458 * @param {Object} [options] Animation options 2459 * @param {Function} [options.onChange] Callback; invoked on every value change 2460 * @param {Function} [options.onComplete] Callback; invoked when value change is completed 2461 * @param {Number} [options.startValue=0] Starting value 2462 * @param {Number} [options.endValue=100] Ending value 2463 * @param {Number} [options.byValue=100] Value to modify the property by 2464 * @param {Function} [options.easing] Easing function 2465 * @param {Number} [options.duration=500] Duration of change (in ms) 2466 */ 2467 function animate(options) { 2468 2469 requestAnimFrame(function(timestamp) { 2470 options || (options = { }); 2471 2472 var start = timestamp || +new Date(), 2473 duration = options.duration || 500, 2474 finish = start + duration, time, 2475 onChange = options.onChange || function() { }, 2476 abort = options.abort || function() { return false; }, 2477 easing = options.easing || function(t, b, c, d) {return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;}, 2478 startValue = 'startValue' in options ? options.startValue : 0, 2479 endValue = 'endValue' in options ? options.endValue : 100, 2480 byValue = options.byValue || endValue - startValue; 2481 2482 options.onStart && options.onStart(); 2483 2484 (function tick(ticktime) { 2485 time = ticktime || +new Date(); 2486 var currentTime = time > finish ? duration : (time - start); 2487 if (abort()) { 2488 options.onComplete && options.onComplete(); 2489 return; 2490 } 2491 onChange(easing(currentTime, startValue, byValue, duration)); 2492 if (time > finish) { 2493 options.onComplete && options.onComplete(); 2494 return; 2495 } 2496 requestAnimFrame(tick); 2497 })(start); 2498 }); 2499 2500 } 2501 2502 var _requestAnimFrame = fabric.window.requestAnimationFrame || 2503 fabric.window.webkitRequestAnimationFrame || 2504 fabric.window.mozRequestAnimationFrame || 2505 fabric.window.oRequestAnimationFrame || 2506 fabric.window.msRequestAnimationFrame || 2507 function(callback) { 2508 fabric.window.setTimeout(callback, 1000 / 60); 2509 }; 2510 2511 /** 2512 * requestAnimationFrame polyfill based on http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 2513 * In order to get a precise start time, `requestAnimFrame` should be called as an entry into the method 2514 * @memberOf fabric.util 2515 * @param {Function} callback Callback to invoke 2516 * @param {DOMElement} element optional Element to associate with animation 2517 */ 2518 function requestAnimFrame() { 2519 return _requestAnimFrame.apply(fabric.window, arguments); 2520 } 2521 2522 fabric.util.animate = animate; 2523 fabric.util.requestAnimFrame = requestAnimFrame; 2524 2525 })(); 2526 2527 2528 (function() { 2529 2530 function normalize(a, c, p, s) { 2531 if (a < Math.abs(c)) { 2532 a = c; 2533 s = p / 4; 2534 } 2535 else { 2536 s = p / (2 * Math.PI) * Math.asin(c / a); 2537 } 2538 return { a: a, c: c, p: p, s: s }; 2539 } 2540 2541 function elastic(opts, t, d) { 2542 return opts.a * 2543 Math.pow(2, 10 * (t -= 1)) * 2544 Math.sin( (t * d - opts.s) * (2 * Math.PI) / opts.p ); 2545 } 2546 2547 /** 2548 * Cubic easing out 2549 * @memberOf fabric.util.ease 2550 */ 2551 function easeOutCubic(t, b, c, d) { 2552 return c * ((t = t / d - 1) * t * t + 1) + b; 2553 } 2554 2555 /** 2556 * Cubic easing in and out 2557 * @memberOf fabric.util.ease 2558 */ 2559 function easeInOutCubic(t, b, c, d) { 2560 t /= d/2; 2561 if (t < 1) { 2562 return c / 2 * t * t * t + b; 2563 } 2564 return c / 2 * ((t -= 2) * t * t + 2) + b; 2565 } 2566 2567 /** 2568 * Quartic easing in 2569 * @memberOf fabric.util.ease 2570 */ 2571 function easeInQuart(t, b, c, d) { 2572 return c * (t /= d) * t * t * t + b; 2573 } 2574 2575 /** 2576 * Quartic easing out 2577 * @memberOf fabric.util.ease 2578 */ 2579 function easeOutQuart(t, b, c, d) { 2580 return -c * ((t = t / d - 1) * t * t * t - 1) + b; 2581 } 2582 2583 /** 2584 * Quartic easing in and out 2585 * @memberOf fabric.util.ease 2586 */ 2587 function easeInOutQuart(t, b, c, d) { 2588 t /= d / 2; 2589 if (t < 1) { 2590 return c / 2 * t * t * t * t + b; 2591 } 2592 return -c / 2 * ((t -= 2) * t * t * t - 2) + b; 2593 } 2594 2595 /** 2596 * Quintic easing in 2597 * @memberOf fabric.util.ease 2598 */ 2599 function easeInQuint(t, b, c, d) { 2600 return c * (t /= d) * t * t * t * t + b; 2601 } 2602 2603 /** 2604 * Quintic easing out 2605 * @memberOf fabric.util.ease 2606 */ 2607 function easeOutQuint(t, b, c, d) { 2608 return c * ((t = t / d - 1) * t * t * t * t + 1) + b; 2609 } 2610 2611 /** 2612 * Quintic easing in and out 2613 * @memberOf fabric.util.ease 2614 */ 2615 function easeInOutQuint(t, b, c, d) { 2616 t /= d / 2; 2617 if (t < 1) { 2618 return c / 2 * t * t * t * t * t + b; 2619 } 2620 return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; 2621 } 2622 2623 /** 2624 * Sinusoidal easing in 2625 * @memberOf fabric.util.ease 2626 */ 2627 function easeInSine(t, b, c, d) { 2628 return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; 2629 } 2630 2631 /** 2632 * Sinusoidal easing out 2633 * @memberOf fabric.util.ease 2634 */ 2635 function easeOutSine(t, b, c, d) { 2636 return c * Math.sin(t / d * (Math.PI / 2)) + b; 2637 } 2638 2639 /** 2640 * Sinusoidal easing in and out 2641 * @memberOf fabric.util.ease 2642 */ 2643 function easeInOutSine(t, b, c, d) { 2644 return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b; 2645 } 2646 2647 /** 2648 * Exponential easing in 2649 * @memberOf fabric.util.ease 2650 */ 2651 function easeInExpo(t, b, c, d) { 2652 return (t === 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b; 2653 } 2654 2655 /** 2656 * Exponential easing out 2657 * @memberOf fabric.util.ease 2658 */ 2659 function easeOutExpo(t, b, c, d) { 2660 return (t === d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b; 2661 } 2662 2663 /** 2664 * Exponential easing in and out 2665 * @memberOf fabric.util.ease 2666 */ 2667 function easeInOutExpo(t, b, c, d) { 2668 if (t === 0) { 2669 return b; 2670 } 2671 if (t === d) { 2672 return b + c; 2673 } 2674 t /= d / 2; 2675 if (t < 1) { 2676 return c / 2 * Math.pow(2, 10 * (t - 1)) + b; 2677 } 2678 return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b; 2679 } 2680 2681 /** 2682 * Circular easing in 2683 * @memberOf fabric.util.ease 2684 */ 2685 function easeInCirc(t, b, c, d) { 2686 return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; 2687 } 2688 2689 /** 2690 * Circular easing out 2691 * @memberOf fabric.util.ease 2692 */ 2693 function easeOutCirc(t, b, c, d) { 2694 return c * Math.sqrt(1 - (t = t / d - 1) * t) + b; 2695 } 2696 2697 /** 2698 * Circular easing in and out 2699 * @memberOf fabric.util.ease 2700 */ 2701 function easeInOutCirc(t, b, c, d) { 2702 t /= d / 2; 2703 if (t < 1) { 2704 return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b; 2705 } 2706 return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b; 2707 } 2708 2709 /** 2710 * Elastic easing in 2711 * @memberOf fabric.util.ease 2712 */ 2713 function easeInElastic(t, b, c, d) { 2714 var s = 1.70158, p = 0, a = c; 2715 if (t === 0) { 2716 return b; 2717 } 2718 t /= d; 2719 if (t === 1) { 2720 return b + c; 2721 } 2722 if (!p) { 2723 p = d * 0.3; 2724 } 2725 var opts = normalize(a, c, p, s); 2726 return -elastic(opts, t, d) + b; 2727 } 2728 2729 /** 2730 * Elastic easing out 2731 * @memberOf fabric.util.ease 2732 */ 2733 function easeOutElastic(t, b, c, d) { 2734 var s = 1.70158, p = 0, a = c; 2735 if (t === 0) { 2736 return b; 2737 } 2738 t /= d; 2739 if (t === 1) { 2740 return b + c; 2741 } 2742 if (!p) { 2743 p = d * 0.3; 2744 } 2745 var opts = normalize(a, c, p, s); 2746 return opts.a * Math.pow(2, -10 * t) * Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) + opts.c + b; 2747 } 2748 2749 /** 2750 * Elastic easing in and out 2751 * @memberOf fabric.util.ease 2752 */ 2753 function easeInOutElastic(t, b, c, d) { 2754 var s = 1.70158, p = 0, a = c; 2755 if (t === 0) { 2756 return b; 2757 } 2758 t /= d / 2; 2759 if (t === 2) { 2760 return b + c; 2761 } 2762 if (!p) { 2763 p = d * (0.3 * 1.5); 2764 } 2765 var opts = normalize(a, c, p, s); 2766 if (t < 1) { 2767 return -0.5 * elastic(opts, t, d) + b; 2768 } 2769 return opts.a * Math.pow(2, -10 * (t -= 1)) * 2770 Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) * 0.5 + opts.c + b; 2771 } 2772 2773 /** 2774 * Backwards easing in 2775 * @memberOf fabric.util.ease 2776 */ 2777 function easeInBack(t, b, c, d, s) { 2778 if (s === undefined) { 2779 s = 1.70158; 2780 } 2781 return c * (t /= d) * t * ((s + 1) * t - s) + b; 2782 } 2783 2784 /** 2785 * Backwards easing out 2786 * @memberOf fabric.util.ease 2787 */ 2788 function easeOutBack(t, b, c, d, s) { 2789 if (s === undefined) { 2790 s = 1.70158; 2791 } 2792 return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; 2793 } 2794 2795 /** 2796 * Backwards easing in and out 2797 * @memberOf fabric.util.ease 2798 */ 2799 function easeInOutBack(t, b, c, d, s) { 2800 if (s === undefined) { 2801 s = 1.70158; 2802 } 2803 t /= d / 2; 2804 if (t < 1) { 2805 return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b; 2806 } 2807 return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b; 2808 } 2809 2810 /** 2811 * Bouncing easing in 2812 * @memberOf fabric.util.ease 2813 */ 2814 function easeInBounce(t, b, c, d) { 2815 return c - easeOutBounce (d - t, 0, c, d) + b; 2816 } 2817 2818 /** 2819 * Bouncing easing out 2820 * @memberOf fabric.util.ease 2821 */ 2822 function easeOutBounce(t, b, c, d) { 2823 if ((t /= d) < (1 / 2.75)) { 2824 return c * (7.5625 * t * t) + b; 2825 } 2826 else if (t < (2/2.75)) { 2827 return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b; 2828 } 2829 else if (t < (2.5/2.75)) { 2830 return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b; 2831 } 2832 else { 2833 return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b; 2834 } 2835 } 2836 2837 /** 2838 * Bouncing easing in and out 2839 * @memberOf fabric.util.ease 2840 */ 2841 function easeInOutBounce(t, b, c, d) { 2842 if (t < d / 2) { 2843 return easeInBounce (t * 2, 0, c, d) * 0.5 + b; 2844 } 2845 return easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; 2846 } 2847 2848 /** 2849 * Easing functions 2850 * See <a href="http://gizma.com/easing/">Easing Equations by Robert Penner</a> 2851 * @namespace fabric.util.ease 2852 */ 2853 fabric.util.ease = { 2854 2855 /** 2856 * Quadratic easing in 2857 * @memberOf fabric.util.ease 2858 */ 2859 easeInQuad: function(t, b, c, d) { 2860 return c * (t /= d) * t + b; 2861 }, 2862 2863 /** 2864 * Quadratic easing out 2865 * @memberOf fabric.util.ease 2866 */ 2867 easeOutQuad: function(t, b, c, d) { 2868 return -c * (t /= d) * (t - 2) + b; 2869 }, 2870 2871 /** 2872 * Quadratic easing in and out 2873 * @memberOf fabric.util.ease 2874 */ 2875 easeInOutQuad: function(t, b, c, d) { 2876 t /= (d / 2); 2877 if (t < 1) { 2878 return c / 2 * t * t + b; 2879 } 2880 return -c / 2 * ((--t) * (t - 2) - 1) + b; 2881 }, 2882 2883 /** 2884 * Cubic easing in 2885 * @memberOf fabric.util.ease 2886 */ 2887 easeInCubic: function(t, b, c, d) { 2888 return c * (t /= d) * t * t + b; 2889 }, 2890 2891 easeOutCubic: easeOutCubic, 2892 easeInOutCubic: easeInOutCubic, 2893 easeInQuart: easeInQuart, 2894 easeOutQuart: easeOutQuart, 2895 easeInOutQuart: easeInOutQuart, 2896 easeInQuint: easeInQuint, 2897 easeOutQuint: easeOutQuint, 2898 easeInOutQuint: easeInOutQuint, 2899 easeInSine: easeInSine, 2900 easeOutSine: easeOutSine, 2901 easeInOutSine: easeInOutSine, 2902 easeInExpo: easeInExpo, 2903 easeOutExpo: easeOutExpo, 2904 easeInOutExpo: easeInOutExpo, 2905 easeInCirc: easeInCirc, 2906 easeOutCirc: easeOutCirc, 2907 easeInOutCirc: easeInOutCirc, 2908 easeInElastic: easeInElastic, 2909 easeOutElastic: easeOutElastic, 2910 easeInOutElastic: easeInOutElastic, 2911 easeInBack: easeInBack, 2912 easeOutBack: easeOutBack, 2913 easeInOutBack: easeInOutBack, 2914 easeInBounce: easeInBounce, 2915 easeOutBounce: easeOutBounce, 2916 easeInOutBounce: easeInOutBounce 2917 }; 2918 2919 }()); 2920 2921 2922 (function(global) { 2923 2924 'use strict'; 2925 2926 /** 2927 * @name fabric 2928 * @namespace 2929 */ 2930 2931 var fabric = global.fabric || (global.fabric = { }), 2932 extend = fabric.util.object.extend, 2933 capitalize = fabric.util.string.capitalize, 2934 clone = fabric.util.object.clone, 2935 toFixed = fabric.util.toFixed, 2936 parseUnit = fabric.util.parseUnit, 2937 multiplyTransformMatrices = fabric.util.multiplyTransformMatrices, 2938 2939 reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/i, 2940 reViewBoxTagNames = /^(symbol|image|marker|pattern|view|svg)$/i, 2941 reNotAllowedAncestors = /^(?:pattern|defs|symbol|metadata)$/i, 2942 reAllowedParents = /^(symbol|g|a|svg)$/i, 2943 2944 attributesMap = { 2945 cx: 'left', 2946 x: 'left', 2947 r: 'radius', 2948 cy: 'top', 2949 y: 'top', 2950 display: 'visible', 2951 visibility: 'visible', 2952 transform: 'transformMatrix', 2953 'fill-opacity': 'fillOpacity', 2954 'fill-rule': 'fillRule', 2955 'font-family': 'fontFamily', 2956 'font-size': 'fontSize', 2957 'font-style': 'fontStyle', 2958 'font-weight': 'fontWeight', 2959 'stroke-dasharray': 'strokeDashArray', 2960 'stroke-linecap': 'strokeLineCap', 2961 'stroke-linejoin': 'strokeLineJoin', 2962 'stroke-miterlimit': 'strokeMiterLimit', 2963 'stroke-opacity': 'strokeOpacity', 2964 'stroke-width': 'strokeWidth', 2965 'text-decoration': 'textDecoration', 2966 'text-anchor': 'originX' 2967 }, 2968 2969 colorAttributes = { 2970 stroke: 'strokeOpacity', 2971 fill: 'fillOpacity' 2972 }; 2973 2974 fabric.cssRules = { }; 2975 fabric.gradientDefs = { }; 2976 2977 function normalizeAttr(attr) { 2978 // transform attribute names 2979 if (attr in attributesMap) { 2980 return attributesMap[attr]; 2981 } 2982 return attr; 2983 } 2984 2985 function normalizeValue(attr, value, parentAttributes, fontSize) { 2986 var isArray = Object.prototype.toString.call(value) === '[object Array]', 2987 parsed; 2988 2989 if ((attr === 'fill' || attr === 'stroke') && value === 'none') { 2990 value = ''; 2991 } 2992 else if (attr === 'strokeDashArray') { 2993 value = value.replace(/,/g, ' ').split(/\s+/).map(function(n) { 2994 return parseFloat(n); 2995 }); 2996 } 2997 else if (attr === 'transformMatrix') { 2998 if (parentAttributes && parentAttributes.transformMatrix) { 2999 value = multiplyTransformMatrices( 3000 parentAttributes.transformMatrix, fabric.parseTransformAttribute(value)); 3001 } 3002 else { 3003 value = fabric.parseTransformAttribute(value); 3004 } 3005 } 3006 else if (attr === 'visible') { 3007 value = (value === 'none' || value === 'hidden') ? false : true; 3008 // display=none on parent element always takes precedence over child element 3009 if (parentAttributes && parentAttributes.visible === false) { 3010 value = false; 3011 } 3012 } 3013 else if (attr === 'originX' /* text-anchor */) { 3014 value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center'; 3015 } 3016 else { 3017 parsed = isArray ? value.map(parseUnit) : parseUnit(value, fontSize); 3018 } 3019 3020 return (!isArray && isNaN(parsed) ? value : parsed); 3021 } 3022 3023 /** 3024 * @private 3025 * @param {Object} attributes Array of attributes to parse 3026 */ 3027 function _setStrokeFillOpacity(attributes) { 3028 for (var attr in colorAttributes) { 3029 3030 if (typeof attributes[colorAttributes[attr]] === 'undefined') { 3031 continue; 3032 } 3033 3034 if (!attributes[attr]) { 3035 if (!fabric.Object.prototype[attr]) { 3036 continue; 3037 } 3038 attributes[attr] = fabric.Object.prototype[attr]; 3039 } 3040 3041 if (attributes[attr].indexOf('url(') === 0) { 3042 continue; 3043 } 3044 3045 var color = new fabric.Color(attributes[attr]); 3046 attributes[attr] = color.setAlpha(toFixed(color.getAlpha() * attributes[colorAttributes[attr]], 2)).toRgba(); 3047 } 3048 return attributes; 3049 } 3050 3051 /** 3052 * Parses "transform" attribute, returning an array of values 3053 * @static 3054 * @function 3055 * @memberOf fabric 3056 * @param {String} attributeValue String containing attribute value 3057 * @return {Array} Array of 6 elements representing transformation matrix 3058 */ 3059 fabric.parseTransformAttribute = (function() { 3060 function rotateMatrix(matrix, args) { 3061 var angle = args[0], 3062 x = (args.length === 3) ? args[1] : 0, 3063 y = (args.length === 3) ? args[2] : 0; 3064 3065 matrix[0] = Math.cos(angle); 3066 matrix[1] = Math.sin(angle); 3067 matrix[2] = -Math.sin(angle); 3068 matrix[3] = Math.cos(angle); 3069 matrix[4] = x - (matrix[0] * x + matrix[2] * y); 3070 matrix[5] = y - (matrix[1] * x + matrix[3] * y); 3071 } 3072 3073 function scaleMatrix(matrix, args) { 3074 var multiplierX = args[0], 3075 multiplierY = (args.length === 2) ? args[1] : args[0]; 3076 3077 matrix[0] = multiplierX; 3078 matrix[3] = multiplierY; 3079 } 3080 3081 function skewXMatrix(matrix, args) { 3082 matrix[2] = Math.tan(fabric.util.degreesToRadians(args[0])); 3083 } 3084 3085 function skewYMatrix(matrix, args) { 3086 matrix[1] = Math.tan(fabric.util.degreesToRadians(args[0])); 3087 } 3088 3089 function translateMatrix(matrix, args) { 3090 matrix[4] = args[0]; 3091 if (args.length === 2) { 3092 matrix[5] = args[1]; 3093 } 3094 } 3095 3096 // identity matrix 3097 var iMatrix = [ 3098 1, // a 3099 0, // b 3100 0, // c 3101 1, // d 3102 0, // e 3103 0 // f 3104 ], 3105 3106 // == begin transform regexp 3107 number = fabric.reNum, 3108 3109 commaWsp = '(?:\\s+,?\\s*|,\\s*)', 3110 3111 skewX = '(?:(skewX)\\s*\\(\\s*(' + number + ')\\s*\\))', 3112 3113 skewY = '(?:(skewY)\\s*\\(\\s*(' + number + ')\\s*\\))', 3114 3115 rotate = '(?:(rotate)\\s*\\(\\s*(' + number + ')(?:' + 3116 commaWsp + '(' + number + ')' + 3117 commaWsp + '(' + number + '))?\\s*\\))', 3118 3119 scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' + 3120 commaWsp + '(' + number + '))?\\s*\\))', 3121 3122 translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' + 3123 commaWsp + '(' + number + '))?\\s*\\))', 3124 3125 matrix = '(?:(matrix)\\s*\\(\\s*' + 3126 '(' + number + ')' + commaWsp + 3127 '(' + number + ')' + commaWsp + 3128 '(' + number + ')' + commaWsp + 3129 '(' + number + ')' + commaWsp + 3130 '(' + number + ')' + commaWsp + 3131 '(' + number + ')' + 3132 '\\s*\\))', 3133 3134 transform = '(?:' + 3135 matrix + '|' + 3136 translate + '|' + 3137 scale + '|' + 3138 rotate + '|' + 3139 skewX + '|' + 3140 skewY + 3141 ')', 3142 3143 transforms = '(?:' + transform + '(?:' + commaWsp + '*' + transform + ')*' + ')', 3144 3145 transformList = '^\\s*(?:' + transforms + '?)\\s*$', 3146 3147 // http://www.w3.org/TR/SVG/coords.html#TransformAttribute 3148 reTransformList = new RegExp(transformList), 3149 // == end transform regexp 3150 3151 reTransform = new RegExp(transform, 'g'); 3152 3153 return function(attributeValue) { 3154 3155 // start with identity matrix 3156 var matrix = iMatrix.concat(), 3157 matrices = [ ]; 3158 3159 // return if no argument was given or 3160 // an argument does not match transform attribute regexp 3161 if (!attributeValue || (attributeValue && !reTransformList.test(attributeValue))) { 3162 return matrix; 3163 } 3164 3165 attributeValue.replace(reTransform, function(match) { 3166 3167 var m = new RegExp(transform).exec(match).filter(function (match) { 3168 return (match !== '' && match != null); 3169 }), 3170 operation = m[1], 3171 args = m.slice(2).map(parseFloat); 3172 3173 switch (operation) { 3174 case 'translate': 3175 translateMatrix(matrix, args); 3176 break; 3177 case 'rotate': 3178 args[0] = fabric.util.degreesToRadians(args[0]); 3179 rotateMatrix(matrix, args); 3180 break; 3181 case 'scale': 3182 scaleMatrix(matrix, args); 3183 break; 3184 case 'skewX': 3185 skewXMatrix(matrix, args); 3186 break; 3187 case 'skewY': 3188 skewYMatrix(matrix, args); 3189 break; 3190 case 'matrix': 3191 matrix = args; 3192 break; 3193 } 3194 3195 // snapshot current matrix into matrices array 3196 matrices.push(matrix.concat()); 3197 // reset 3198 matrix = iMatrix.concat(); 3199 }); 3200 3201 var combinedMatrix = matrices[0]; 3202 while (matrices.length > 1) { 3203 matrices.shift(); 3204 combinedMatrix = fabric.util.multiplyTransformMatrices(combinedMatrix, matrices[0]); 3205 } 3206 return combinedMatrix; 3207 }; 3208 })(); 3209 3210 /** 3211 * @private 3212 */ 3213 function parseStyleString(style, oStyle) { 3214 var attr, value; 3215 style.replace(/;\s*$/, '').split(';').forEach(function (chunk) { 3216 var pair = chunk.split(':'); 3217 3218 attr = normalizeAttr(pair[0].trim().toLowerCase()); 3219 value = normalizeValue(attr, pair[1].trim()); 3220 3221 oStyle[attr] = value; 3222 }); 3223 } 3224 3225 /** 3226 * @private 3227 */ 3228 function parseStyleObject(style, oStyle) { 3229 var attr, value; 3230 for (var prop in style) { 3231 if (typeof style[prop] === 'undefined') { 3232 continue; 3233 } 3234 3235 attr = normalizeAttr(prop.toLowerCase()); 3236 value = normalizeValue(attr, style[prop]); 3237 3238 oStyle[attr] = value; 3239 } 3240 } 3241 3242 /** 3243 * @private 3244 */ 3245 function getGlobalStylesForElement(element, svgUid) { 3246 var styles = { }; 3247 for (var rule in fabric.cssRules[svgUid]) { 3248 if (elementMatchesRule(element, rule.split(' '))) { 3249 for (var property in fabric.cssRules[svgUid][rule]) { 3250 styles[property] = fabric.cssRules[svgUid][rule][property]; 3251 } 3252 } 3253 } 3254 return styles; 3255 } 3256 3257 /** 3258 * @private 3259 */ 3260 function elementMatchesRule(element, selectors) { 3261 var firstMatching, parentMatching = true; 3262 //start from rightmost selector. 3263 firstMatching = selectorMatches(element, selectors.pop()); 3264 if (firstMatching && selectors.length) { 3265 parentMatching = doesSomeParentMatch(element, selectors); 3266 } 3267 return firstMatching && parentMatching && (selectors.length === 0); 3268 } 3269 3270 function doesSomeParentMatch(element, selectors) { 3271 var selector, parentMatching = true; 3272 while (element.parentNode && element.parentNode.nodeType === 1 && selectors.length) { 3273 if (parentMatching) { 3274 selector = selectors.pop(); 3275 } 3276 element = element.parentNode; 3277 parentMatching = selectorMatches(element, selector); 3278 } 3279 return selectors.length === 0; 3280 } 3281 3282 /** 3283 * @private 3284 */ 3285 function selectorMatches(element, selector) { 3286 var nodeName = element.nodeName, 3287 classNames = element.getAttribute('class'), 3288 id = element.getAttribute('id'), matcher; 3289 // i check if a selector matches slicing away part from it. 3290 // if i get empty string i should match 3291 matcher = new RegExp('^' + nodeName, 'i'); 3292 selector = selector.replace(matcher, ''); 3293 if (id && selector.length) { 3294 matcher = new RegExp('#' + id + '(?![a-zA-Z\\-]+)', 'i'); 3295 selector = selector.replace(matcher, ''); 3296 } 3297 if (classNames && selector.length) { 3298 classNames = classNames.split(' '); 3299 for (var i = classNames.length; i--;) { 3300 matcher = new RegExp('\\.' + classNames[i] + '(?![a-zA-Z\\-]+)', 'i'); 3301 selector = selector.replace(matcher, ''); 3302 } 3303 } 3304 return selector.length === 0; 3305 } 3306 3307 /** 3308 * @private 3309 * to support IE8 missing getElementById on SVGdocument 3310 */ 3311 function elementById(doc, id) { 3312 var el; 3313 doc.getElementById && (el = doc.getElementById(id)); 3314 if (el) { 3315 return el; 3316 } 3317 var node, i, nodelist = doc.getElementsByTagName('*'); 3318 for (i = 0; i < nodelist.length; i++) { 3319 node = nodelist[i]; 3320 if (id === node.getAttribute('id')) { 3321 return node; 3322 } 3323 } 3324 } 3325 3326 /** 3327 * @private 3328 */ 3329 function parseUseDirectives(doc) { 3330 var nodelist = doc.getElementsByTagName('use'), i = 0; 3331 while (nodelist.length && i < nodelist.length) { 3332 var el = nodelist[i], 3333 xlink = el.getAttribute('xlink:href').substr(1), 3334 x = el.getAttribute('x') || 0, 3335 y = el.getAttribute('y') || 0, 3336 el2 = elementById(doc, xlink).cloneNode(true), 3337 currentTrans = (el2.getAttribute('transform') || '') + ' translate(' + x + ', ' + y + ')', 3338 parentNode, oldLength = nodelist.length, attr, j, attrs, l; 3339 3340 applyViewboxTransform(el2); 3341 if (/^svg$/i.test(el2.nodeName)) { 3342 var el3 = el2.ownerDocument.createElement('g'); 3343 for (j = 0, attrs = el2.attributes, l = attrs.length; j < l; j++) { 3344 attr = attrs.item(j); 3345 el3.setAttribute(attr.nodeName, attr.nodeValue); 3346 } 3347 while (el2.firstChild != null) { 3348 el3.appendChild(el2.firstChild); 3349 } 3350 el2 = el3; 3351 } 3352 3353 for (j = 0, attrs = el.attributes, l = attrs.length; j < l; j++) { 3354 attr = attrs.item(j); 3355 if (attr.nodeName === 'x' || attr.nodeName === 'y' || attr.nodeName === 'xlink:href') { 3356 continue; 3357 } 3358 3359 if (attr.nodeName === 'transform') { 3360 currentTrans = attr.nodeValue + ' ' + currentTrans; 3361 } 3362 else { 3363 el2.setAttribute(attr.nodeName, attr.nodeValue); 3364 } 3365 } 3366 3367 el2.setAttribute('transform', currentTrans); 3368 el2.setAttribute('instantiated_by_use', '1'); 3369 el2.removeAttribute('id'); 3370 parentNode = el.parentNode; 3371 parentNode.replaceChild(el2, el); 3372 // some browsers do not shorten nodelist after replaceChild (IE8) 3373 if (nodelist.length === oldLength) { 3374 i++; 3375 } 3376 } 3377 } 3378 3379 // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute 3380 // matches, e.g.: +14.56e-12, etc. 3381 var reViewBoxAttrValue = new RegExp( 3382 '^' + 3383 '\\s*(' + fabric.reNum + '+)\\s*,?' + 3384 '\\s*(' + fabric.reNum + '+)\\s*,?' + 3385 '\\s*(' + fabric.reNum + '+)\\s*,?' + 3386 '\\s*(' + fabric.reNum + '+)\\s*' + 3387 '$' 3388 ); 3389 3390 /** 3391 * Add a <g> element that envelop all child elements and makes the viewbox transformMatrix descend on all elements 3392 */ 3393 function applyViewboxTransform(element) { 3394 3395 var viewBoxAttr = element.getAttribute('viewBox'), 3396 scaleX = 1, 3397 scaleY = 1, 3398 minX = 0, 3399 minY = 0, 3400 viewBoxWidth, viewBoxHeight, matrix, el, 3401 widthAttr = element.getAttribute('width'), 3402 heightAttr = element.getAttribute('height'), 3403 x = element.getAttribute('x') || 0, 3404 y = element.getAttribute('y') || 0, 3405 preserveAspectRatio = element.getAttribute('preserveAspectRatio') || '', 3406 missingViewBox = (!viewBoxAttr || !reViewBoxTagNames.test(element.tagName) 3407 || !(viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))), 3408 missingDimAttr = (!widthAttr || !heightAttr || widthAttr === '100%' || heightAttr === '100%'), 3409 toBeParsed = missingViewBox && missingDimAttr, 3410 parsedDim = { }, translateMatrix = ''; 3411 3412 parsedDim.width = 0; 3413 parsedDim.height = 0; 3414 parsedDim.toBeParsed = toBeParsed; 3415 3416 if (toBeParsed) { 3417 return parsedDim; 3418 } 3419 3420 if (missingViewBox) { 3421 parsedDim.width = parseUnit(widthAttr); 3422 parsedDim.height = parseUnit(heightAttr); 3423 return parsedDim; 3424 } 3425 3426 minX = -parseFloat(viewBoxAttr[1]), 3427 minY = -parseFloat(viewBoxAttr[2]), 3428 viewBoxWidth = parseFloat(viewBoxAttr[3]), 3429 viewBoxHeight = parseFloat(viewBoxAttr[4]); 3430 3431 if (!missingDimAttr) { 3432 parsedDim.width = parseUnit(widthAttr); 3433 parsedDim.height = parseUnit(heightAttr); 3434 scaleX = parsedDim.width / viewBoxWidth; 3435 scaleY = parsedDim.height / viewBoxHeight; 3436 } 3437 else { 3438 parsedDim.width = viewBoxWidth; 3439 parsedDim.height = viewBoxHeight; 3440 } 3441 3442 // default is to preserve aspect ratio 3443 preserveAspectRatio = fabric.util.parsePreserveAspectRatioAttribute(preserveAspectRatio); 3444 if (preserveAspectRatio.alignX !== 'none') { 3445 //translate all container for the effect of Mid, Min, Max 3446 scaleY = scaleX = (scaleX > scaleY ? scaleY : scaleX); 3447 } 3448 3449 if (scaleX === 1 && scaleY === 1 && minX === 0 && minY === 0 && x === 0 && y === 0) { 3450 return parsedDim; 3451 } 3452 3453 if (x || y) { 3454 translateMatrix = ' translate(' + parseUnit(x) + ' ' + parseUnit(y) + ') '; 3455 } 3456 3457 matrix = translateMatrix + ' matrix(' + scaleX + 3458 ' 0' + 3459 ' 0 ' + 3460 scaleY + ' ' + 3461 (minX * scaleX) + ' ' + 3462 (minY * scaleY) + ') '; 3463 3464 if (element.tagName === 'svg') { 3465 el = element.ownerDocument.createElement('g'); 3466 while (element.firstChild != null) { 3467 el.appendChild(element.firstChild); 3468 } 3469 element.appendChild(el); 3470 } 3471 else { 3472 el = element; 3473 matrix = el.getAttribute('transform') + matrix; 3474 } 3475 3476 el.setAttribute('transform', matrix); 3477 return parsedDim; 3478 } 3479 3480 /** 3481 * Parses an SVG document, converts it to an array of corresponding fabric.* instances and passes them to a callback 3482 * @static 3483 * @function 3484 * @memberOf fabric 3485 * @param {SVGDocument} doc SVG document to parse 3486 * @param {Function} callback Callback to call when parsing is finished; It's being passed an array of elements (parsed from a document). 3487 * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. 3488 */ 3489 fabric.parseSVGDocument = (function() { 3490 3491 function hasAncestorWithNodeName(element, nodeName) { 3492 while (element && (element = element.parentNode)) { 3493 if (nodeName.test(element.nodeName) && !element.getAttribute('instantiated_by_use')) { 3494 return true; 3495 } 3496 } 3497 return false; 3498 } 3499 3500 return function(doc, callback, reviver) { 3501 if (!doc) { 3502 return; 3503 } 3504 3505 parseUseDirectives(doc); 3506 3507 var startTime = new Date(), 3508 svgUid = fabric.Object.__uid++, 3509 options = applyViewboxTransform(doc), 3510 descendants = fabric.util.toArray(doc.getElementsByTagName('*')); 3511 3512 options.svgUid = svgUid; 3513 3514 if (descendants.length === 0 && fabric.isLikelyNode) { 3515 // we're likely in node, where "o3-xml" library fails to gEBTN("*") 3516 // https://github.com/ajaxorg/node-o3-xml/issues/21 3517 descendants = doc.selectNodes('//*[name(.)!="svg"]'); 3518 var arr = [ ]; 3519 for (var i = 0, len = descendants.length; i < len; i++) { 3520 arr[i] = descendants[i]; 3521 } 3522 descendants = arr; 3523 } 3524 3525 var elements = descendants.filter(function(el) { 3526 applyViewboxTransform(el); 3527 return reAllowedSVGTagNames.test(el.tagName) && 3528 !hasAncestorWithNodeName(el, reNotAllowedAncestors); // http://www.w3.org/TR/SVG/struct.html#DefsElement 3529 }); 3530 3531 if (!elements || (elements && !elements.length)) { 3532 callback && callback([], {}); 3533 return; 3534 } 3535 3536 fabric.gradientDefs[svgUid] = fabric.getGradientDefs(doc); 3537 fabric.cssRules[svgUid] = fabric.getCSSRules(doc); 3538 // Precedence of rules: style > class > attribute 3539 fabric.parseElements(elements, function(instances) { 3540 fabric.documentParsingTime = new Date() - startTime; 3541 if (callback) { 3542 callback(instances, options); 3543 } 3544 }, clone(options), reviver); 3545 }; 3546 })(); 3547 3548 /** 3549 * Used for caching SVG documents (loaded via `fabric.Canvas#loadSVGFromURL`) 3550 * @namespace 3551 */ 3552 var svgCache = { 3553 3554 /** 3555 * @param {String} name 3556 * @param {Function} callback 3557 */ 3558 has: function (name, callback) { 3559 callback(false); 3560 }, 3561 3562 get: function () { 3563 /* NOOP */ 3564 }, 3565 3566 set: function () { 3567 /* NOOP */ 3568 } 3569 }; 3570 3571 /** 3572 * @private 3573 */ 3574 function _enlivenCachedObject(cachedObject) { 3575 3576 var objects = cachedObject.objects, 3577 options = cachedObject.options; 3578 3579 objects = objects.map(function (o) { 3580 return fabric[capitalize(o.type)].fromObject(o); 3581 }); 3582 3583 return ({ objects: objects, options: options }); 3584 } 3585 3586 /** 3587 * @private 3588 */ 3589 function _createSVGPattern(markup, canvas, property) { 3590 if (canvas[property] && canvas[property].toSVG) { 3591 markup.push( 3592 '\t<pattern x="0" y="0" id="', property, 'Pattern" ', 3593 'width="', canvas[property].source.width, 3594 '" height="', canvas[property].source.height, 3595 '" patternUnits="userSpaceOnUse">\n', 3596 '\t\t<image x="0" y="0" ', 3597 'width="', canvas[property].source.width, 3598 '" height="', canvas[property].source.height, 3599 '" xlink:href="', canvas[property].source.src, 3600 '"></image>\n\t</pattern>\n' 3601 ); 3602 } 3603 } 3604 3605 var reFontDeclaration = new RegExp( 3606 '(normal|italic)?\\s*(normal|small-caps)?\\s*' + 3607 '(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\\s*(' + 3608 fabric.reNum + 3609 '(?:px|cm|mm|em|pt|pc|in)*)(?:\\/(normal|' + fabric.reNum + '))?\\s+(.*)'); 3610 3611 extend(fabric, { 3612 /** 3613 * Parses a short font declaration, building adding its properties to a style object 3614 * @static 3615 * @function 3616 * @memberOf fabric 3617 * @param {String} value font declaration 3618 * @param {Object} oStyle definition 3619 */ 3620 parseFontDeclaration: function(value, oStyle) { 3621 var match = value.match(reFontDeclaration); 3622 3623 if (!match) { 3624 return; 3625 } 3626 var fontStyle = match[1], 3627 // font variant is not used 3628 // fontVariant = match[2], 3629 fontWeight = match[3], 3630 fontSize = match[4], 3631 lineHeight = match[5], 3632 fontFamily = match[6]; 3633 3634 if (fontStyle) { 3635 oStyle.fontStyle = fontStyle; 3636 } 3637 if (fontWeight) { 3638 oStyle.fontWeight = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight); 3639 } 3640 if (fontSize) { 3641 oStyle.fontSize = parseUnit(fontSize); 3642 } 3643 if (fontFamily) { 3644 oStyle.fontFamily = fontFamily; 3645 } 3646 if (lineHeight) { 3647 oStyle.lineHeight = lineHeight === 'normal' ? 1 : lineHeight; 3648 } 3649 }, 3650 3651 /** 3652 * Parses an SVG document, returning all of the gradient declarations found in it 3653 * @static 3654 * @function 3655 * @memberOf fabric 3656 * @param {SVGDocument} doc SVG document to parse 3657 * @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition element 3658 */ 3659 getGradientDefs: function(doc) { 3660 var linearGradientEls = doc.getElementsByTagName('linearGradient'), 3661 radialGradientEls = doc.getElementsByTagName('radialGradient'), 3662 el, i, j = 0, id, xlink, elList = [ ], 3663 gradientDefs = { }, idsToXlinkMap = { }; 3664 3665 elList.length = linearGradientEls.length + radialGradientEls.length; 3666 i = linearGradientEls.length; 3667 while (i--) { 3668 elList[j++] = linearGradientEls[i]; 3669 } 3670 i = radialGradientEls.length; 3671 while (i--) { 3672 elList[j++] = radialGradientEls[i]; 3673 } 3674 3675 while (j--) { 3676 el = elList[j]; 3677 xlink = el.getAttribute('xlink:href'); 3678 id = el.getAttribute('id'); 3679 if (xlink) { 3680 idsToXlinkMap[id] = xlink.substr(1); 3681 } 3682 gradientDefs[id] = el; 3683 } 3684 3685 for (id in idsToXlinkMap) { 3686 var el2 = gradientDefs[idsToXlinkMap[id]].cloneNode(true); 3687 el = gradientDefs[id]; 3688 while (el2.firstChild) { 3689 el.appendChild(el2.firstChild); 3690 } 3691 } 3692 return gradientDefs; 3693 }, 3694 3695 /** 3696 * Returns an object of attributes' name/value, given element and an array of attribute names; 3697 * Parses parent "g" nodes recursively upwards. 3698 * @static 3699 * @memberOf fabric 3700 * @param {DOMElement} element Element to parse 3701 * @param {Array} attributes Array of attributes to parse 3702 * @return {Object} object containing parsed attributes' names/values 3703 */ 3704 parseAttributes: function(element, attributes, svgUid) { 3705 3706 if (!element) { 3707 return; 3708 } 3709 3710 var value, 3711 parentAttributes = { }, 3712 fontSize; 3713 3714 if (typeof svgUid === 'undefined') { 3715 svgUid = element.getAttribute('svgUid'); 3716 } 3717 // if there's a parent container (`g` or `a` or `symbol` node), parse its attributes recursively upwards 3718 if (element.parentNode && reAllowedParents.test(element.parentNode.nodeName)) { 3719 parentAttributes = fabric.parseAttributes(element.parentNode, attributes, svgUid); 3720 } 3721 fontSize = (parentAttributes && parentAttributes.fontSize ) || 3722 element.getAttribute('font-size') || fabric.Text.DEFAULT_SVG_FONT_SIZE; 3723 3724 var ownAttributes = attributes.reduce(function(memo, attr) { 3725 value = element.getAttribute(attr); 3726 if (value) { 3727 attr = normalizeAttr(attr); 3728 value = normalizeValue(attr, value, parentAttributes, fontSize); 3729 3730 memo[attr] = value; 3731 } 3732 return memo; 3733 }, { }); 3734 3735 // add values parsed from style, which take precedence over attributes 3736 // (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes) 3737 ownAttributes = extend(ownAttributes, 3738 extend(getGlobalStylesForElement(element, svgUid), fabric.parseStyleAttribute(element))); 3739 if (ownAttributes.font) { 3740 fabric.parseFontDeclaration(ownAttributes.font, ownAttributes); 3741 } 3742 return _setStrokeFillOpacity(extend(parentAttributes, ownAttributes)); 3743 }, 3744 3745 /** 3746 * Transforms an array of svg elements to corresponding fabric.* instances 3747 * @static 3748 * @memberOf fabric 3749 * @param {Array} elements Array of elements to parse 3750 * @param {Function} callback Being passed an array of fabric instances (transformed from SVG elements) 3751 * @param {Object} [options] Options object 3752 * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. 3753 */ 3754 parseElements: function(elements, callback, options, reviver) { 3755 new fabric.ElementsParser(elements, callback, options, reviver).parse(); 3756 }, 3757 3758 /** 3759 * Parses "style" attribute, retuning an object with values 3760 * @static 3761 * @memberOf fabric 3762 * @param {SVGElement} element Element to parse 3763 * @return {Object} Objects with values parsed from style attribute of an element 3764 */ 3765 parseStyleAttribute: function(element) { 3766 var oStyle = { }, 3767 style = element.getAttribute('style'); 3768 3769 if (!style) { 3770 return oStyle; 3771 } 3772 3773 if (typeof style === 'string') { 3774 parseStyleString(style, oStyle); 3775 } 3776 else { 3777 parseStyleObject(style, oStyle); 3778 } 3779 3780 return oStyle; 3781 }, 3782 3783 /** 3784 * Parses "points" attribute, returning an array of values 3785 * @static 3786 * @memberOf fabric 3787 * @param {String} points points attribute string 3788 * @return {Array} array of points 3789 */ 3790 parsePointsAttribute: function(points) { 3791 3792 // points attribute is required and must not be empty 3793 if (!points) { 3794 return null; 3795 } 3796 3797 // replace commas with whitespace and remove bookending whitespace 3798 points = points.replace(/,/g, ' ').trim(); 3799 3800 points = points.split(/\s+/); 3801 var parsedPoints = [ ], i, len; 3802 3803 i = 0; 3804 len = points.length; 3805 for (; i < len; i+=2) { 3806 parsedPoints.push({ 3807 x: parseFloat(points[i]), 3808 y: parseFloat(points[i + 1]) 3809 }); 3810 } 3811 3812 // odd number of points is an error 3813 // if (parsedPoints.length % 2 !== 0) { 3814 // return null; 3815 // } 3816 3817 return parsedPoints; 3818 }, 3819 3820 /** 3821 * Returns CSS rules for a given SVG document 3822 * @static 3823 * @function 3824 * @memberOf fabric 3825 * @param {SVGDocument} doc SVG document to parse 3826 * @return {Object} CSS rules of this document 3827 */ 3828 getCSSRules: function(doc) { 3829 var styles = doc.getElementsByTagName('style'), 3830 allRules = { }, rules; 3831 3832 // very crude parsing of style contents 3833 for (var i = 0, len = styles.length; i < len; i++) { 3834 // IE9 doesn't support textContent, but provides text instead. 3835 var styleContents = styles[i].textContent || styles[i].text; 3836 3837 // remove comments 3838 styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, ''); 3839 if (styleContents.trim() === '') { 3840 continue; 3841 } 3842 rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g); 3843 rules = rules.map(function(rule) { return rule.trim(); }); 3844 3845 rules.forEach(function(rule) { 3846 3847 var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/), 3848 ruleObj = { }, declaration = match[2].trim(), 3849 propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/); 3850 3851 for (var i = 0, len = propertyValuePairs.length; i < len; i++) { 3852 var pair = propertyValuePairs[i].split(/\s*:\s*/), 3853 property = normalizeAttr(pair[0]), 3854 value = normalizeValue(property, pair[1], pair[0]); 3855 ruleObj[property] = value; 3856 } 3857 rule = match[1]; 3858 rule.split(',').forEach(function(_rule) { 3859 _rule = _rule.replace(/^svg/i, '').trim(); 3860 if (_rule === '') { 3861 return; 3862 } 3863 allRules[_rule] = fabric.util.object.clone(ruleObj); 3864 }); 3865 }); 3866 } 3867 return allRules; 3868 }, 3869 3870 /** 3871 * Takes url corresponding to an SVG document, and parses it into a set of fabric objects. Note that SVG is fetched via XMLHttpRequest, so it needs to conform to SOP (Same Origin Policy) 3872 * @memberOf fabric 3873 * @param {String} url 3874 * @param {Function} callback 3875 * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. 3876 */ 3877 loadSVGFromURL: function(url, callback, reviver) { 3878 3879 url = url.replace(/^\n\s*/, '').trim(); 3880 svgCache.has(url, function (hasUrl) { 3881 if (hasUrl) { 3882 svgCache.get(url, function (value) { 3883 var enlivedRecord = _enlivenCachedObject(value); 3884 callback(enlivedRecord.objects, enlivedRecord.options); 3885 }); 3886 } 3887 else { 3888 new fabric.util.request(url, { 3889 method: 'get', 3890 onComplete: onComplete 3891 }); 3892 } 3893 }); 3894 3895 function onComplete(r) { 3896 3897 var xml = r.responseXML; 3898 if (xml && !xml.documentElement && fabric.window.ActiveXObject && r.responseText) { 3899 xml = new ActiveXObject('Microsoft.XMLDOM'); 3900 xml.async = 'false'; 3901 //IE chokes on DOCTYPE 3902 xml.loadXML(r.responseText.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i, '')); 3903 } 3904 if (!xml || !xml.documentElement) { 3905 return; 3906 } 3907 3908 fabric.parseSVGDocument(xml.documentElement, function (results, options) { 3909 svgCache.set(url, { 3910 objects: fabric.util.array.invoke(results, 'toObject'), 3911 options: options 3912 }); 3913 callback(results, options); 3914 }, reviver); 3915 } 3916 }, 3917 3918 /** 3919 * Takes string corresponding to an SVG document, and parses it into a set of fabric objects 3920 * @memberOf fabric 3921 * @param {String} string 3922 * @param {Function} callback 3923 * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. 3924 */ 3925 loadSVGFromString: function(string, callback, reviver) { 3926 string = string.trim(); 3927 var doc; 3928 if (typeof DOMParser !== 'undefined') { 3929 var parser = new DOMParser(); 3930 if (parser && parser.parseFromString) { 3931 doc = parser.parseFromString(string, 'text/xml'); 3932 } 3933 } 3934 else if (fabric.window.ActiveXObject) { 3935 doc = new ActiveXObject('Microsoft.XMLDOM'); 3936 doc.async = 'false'; 3937 // IE chokes on DOCTYPE 3938 doc.loadXML(string.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i, '')); 3939 } 3940 3941 fabric.parseSVGDocument(doc.documentElement, function (results, options) { 3942 callback(results, options); 3943 }, reviver); 3944 }, 3945 3946 /** 3947 * Creates markup containing SVG font faces, 3948 * font URLs for font faces must be collected by developers 3949 * and are not extracted from the DOM by fabricjs 3950 * @param {Array} objects Array of fabric objects 3951 * @return {String} 3952 */ 3953 createSVGFontFacesMarkup: function(objects) { 3954 var markup = '', fontList = { }, obj, fontFamily, 3955 style, row, rowIndex, char, charIndex, 3956 fontPaths = fabric.fontPaths; 3957 3958 for (var i = 0, len = objects.length; i < len; i++) { 3959 obj = objects[i]; 3960 fontFamily = obj.fontFamily; 3961 if (obj.type.indexOf('text') === -1 || fontList[fontFamily] || !fontPaths[fontFamily]) { 3962 continue; 3963 } 3964 fontList[fontFamily] = true; 3965 if (!obj.styles) { 3966 continue; 3967 } 3968 style = obj.styles; 3969 for (rowIndex in style) { 3970 char = style[rowIndex]; 3971 for (charIndex in row) { 3972 char = row[charIndex]; 3973 fontFamily = char.fontFamily; 3974 if (!fontList[fontFamily] && fontPaths[fontFamily]) { 3975 fontList[fontFamily] = true; 3976 } 3977 } 3978 } 3979 } 3980 3981 for (var j in fontList) { 3982 markup += [ 3983 //jscs:disable validateIndentation 3984 '\t\t@font-face {\n', 3985 '\t\t\tfont-family: \'', j, '\';\n', 3986 '\t\t\tsrc: url(\'', fontPaths[j], '\');\n', 3987 '\t\t}\n' 3988 //jscs:enable validateIndentation 3989 ].join(''); 3990 } 3991 3992 if (markup) { 3993 markup = [ 3994 //jscs:disable validateIndentation 3995 '\t<style type="text/css">', 3996 '<![CDATA[\n', 3997 markup, 3998 ']]>', 3999 '</style>\n' 4000 //jscs:enable validateIndentation 4001 ].join(''); 4002 } 4003 4004 return markup; 4005 }, 4006 4007 /** 4008 * Creates markup containing SVG referenced elements like patterns, gradients etc. 4009 * @param {fabric.Canvas} canvas instance of fabric.Canvas 4010 * @return {String} 4011 */ 4012 createSVGRefElementsMarkup: function(canvas) { 4013 var markup = [ ]; 4014 4015 _createSVGPattern(markup, canvas, 'backgroundColor'); 4016 _createSVGPattern(markup, canvas, 'overlayColor'); 4017 4018 return markup.join(''); 4019 } 4020 }); 4021 4022 })(typeof exports !== 'undefined' ? exports : this); 4023 4024 4025 fabric.ElementsParser = function(elements, callback, options, reviver) { 4026 this.elements = elements; 4027 this.callback = callback; 4028 this.options = options; 4029 this.reviver = reviver; 4030 this.svgUid = (options && options.svgUid) || 0; 4031 }; 4032 4033 fabric.ElementsParser.prototype.parse = function() { 4034 this.instances = new Array(this.elements.length); 4035 this.numElements = this.elements.length; 4036 4037 this.createObjects(); 4038 }; 4039 4040 fabric.ElementsParser.prototype.createObjects = function() { 4041 for (var i = 0, len = this.elements.length; i < len; i++) { 4042 this.elements[i].setAttribute('svgUid', this.svgUid); 4043 (function(_this, i) { 4044 setTimeout(function() { 4045 _this.createObject(_this.elements[i], i); 4046 }, 0); 4047 })(this, i); 4048 } 4049 }; 4050 4051 fabric.ElementsParser.prototype.createObject = function(el, index) { 4052 var klass = fabric[fabric.util.string.capitalize(el.tagName)]; 4053 if (klass && klass.fromElement) { 4054 try { 4055 this._createObject(klass, el, index); 4056 } 4057 catch (err) { 4058 fabric.log(err); 4059 } 4060 } 4061 else { 4062 this.checkIfDone(); 4063 } 4064 }; 4065 4066 fabric.ElementsParser.prototype._createObject = function(klass, el, index) { 4067 if (klass.async) { 4068 klass.fromElement(el, this.createCallback(index, el), this.options); 4069 } 4070 else { 4071 var obj = klass.fromElement(el, this.options); 4072 this.resolveGradient(obj, 'fill'); 4073 this.resolveGradient(obj, 'stroke'); 4074 this.reviver && this.reviver(el, obj); 4075 this.instances[index] = obj; 4076 this.checkIfDone(); 4077 } 4078 }; 4079 4080 fabric.ElementsParser.prototype.createCallback = function(index, el) { 4081 var _this = this; 4082 return function(obj) { 4083 _this.resolveGradient(obj, 'fill'); 4084 _this.resolveGradient(obj, 'stroke'); 4085 _this.reviver && _this.reviver(el, obj); 4086 _this.instances[index] = obj; 4087 _this.checkIfDone(); 4088 }; 4089 }; 4090 4091 fabric.ElementsParser.prototype.resolveGradient = function(obj, property) { 4092 4093 var instanceFillValue = obj.get(property); 4094 if (!(/^url\(/).test(instanceFillValue)) { 4095 return; 4096 } 4097 var gradientId = instanceFillValue.slice(5, instanceFillValue.length - 1); 4098 if (fabric.gradientDefs[this.svgUid][gradientId]) { 4099 obj.set(property, 4100 fabric.Gradient.fromElement(fabric.gradientDefs[this.svgUid][gradientId], obj)); 4101 } 4102 }; 4103 4104 fabric.ElementsParser.prototype.checkIfDone = function() { 4105 if (--this.numElements === 0) { 4106 this.instances = this.instances.filter(function(el) { 4107 return el != null; 4108 }); 4109 this.callback(this.instances); 4110 } 4111 }; 4112 4113 4114 (function(global) { 4115 4116 'use strict'; 4117 4118 /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ 4119 4120 var fabric = global.fabric || (global.fabric = { }); 4121 4122 if (fabric.Point) { 4123 fabric.warn('fabric.Point is already defined'); 4124 return; 4125 } 4126 4127 fabric.Point = Point; 4128 4129 /** 4130 * Point class 4131 * @class fabric.Point 4132 * @memberOf fabric 4133 * @constructor 4134 * @param {Number} x 4135 * @param {Number} y 4136 * @return {fabric.Point} thisArg 4137 */ 4138 function Point(x, y) { 4139 this.x = x; 4140 this.y = y; 4141 } 4142 4143 Point.prototype = /** @lends fabric.Point.prototype */ { 4144 4145 constructor: Point, 4146 4147 /** 4148 * Adds another point to this one and returns another one 4149 * @param {fabric.Point} that 4150 * @return {fabric.Point} new Point instance with added values 4151 */ 4152 add: function (that) { 4153 return new Point(this.x + that.x, this.y + that.y); 4154 }, 4155 4156 /** 4157 * Adds another point to this one 4158 * @param {fabric.Point} that 4159 * @return {fabric.Point} thisArg 4160 */ 4161 addEquals: function (that) { 4162 this.x += that.x; 4163 this.y += that.y; 4164 return this; 4165 }, 4166 4167 /** 4168 * Adds value to this point and returns a new one 4169 * @param {Number} scalar 4170 * @return {fabric.Point} new Point with added value 4171 */ 4172 scalarAdd: function (scalar) { 4173 return new Point(this.x + scalar, this.y + scalar); 4174 }, 4175 4176 /** 4177 * Adds value to this point 4178 * @param {Number} scalar 4179 * @return {fabric.Point} thisArg 4180 */ 4181 scalarAddEquals: function (scalar) { 4182 this.x += scalar; 4183 this.y += scalar; 4184 return this; 4185 }, 4186 4187 /** 4188 * Subtracts another point from this point and returns a new one 4189 * @param {fabric.Point} that 4190 * @return {fabric.Point} new Point object with subtracted values 4191 */ 4192 subtract: function (that) { 4193 return new Point(this.x - that.x, this.y - that.y); 4194 }, 4195 4196 /** 4197 * Subtracts another point from this point 4198 * @param {fabric.Point} that 4199 * @return {fabric.Point} thisArg 4200 */ 4201 subtractEquals: function (that) { 4202 this.x -= that.x; 4203 this.y -= that.y; 4204 return this; 4205 }, 4206 4207 /** 4208 * Subtracts value from this point and returns a new one 4209 * @param {Number} scalar 4210 * @return {fabric.Point} 4211 */ 4212 scalarSubtract: function (scalar) { 4213 return new Point(this.x - scalar, this.y - scalar); 4214 }, 4215 4216 /** 4217 * Subtracts value from this point 4218 * @param {Number} scalar 4219 * @return {fabric.Point} thisArg 4220 */ 4221 scalarSubtractEquals: function (scalar) { 4222 this.x -= scalar; 4223 this.y -= scalar; 4224 return this; 4225 }, 4226 4227 /** 4228 * Miltiplies this point by a value and returns a new one 4229 * @param {Number} scalar 4230 * @return {fabric.Point} 4231 */ 4232 multiply: function (scalar) { 4233 return new Point(this.x * scalar, this.y * scalar); 4234 }, 4235 4236 /** 4237 * Miltiplies this point by a value 4238 * @param {Number} scalar 4239 * @return {fabric.Point} thisArg 4240 */ 4241 multiplyEquals: function (scalar) { 4242 this.x *= scalar; 4243 this.y *= scalar; 4244 return this; 4245 }, 4246 4247 /** 4248 * Divides this point by a value and returns a new one 4249 * @param {Number} scalar 4250 * @return {fabric.Point} 4251 */ 4252 divide: function (scalar) { 4253 return new Point(this.x / scalar, this.y / scalar); 4254 }, 4255 4256 /** 4257 * Divides this point by a value 4258 * @param {Number} scalar 4259 * @return {fabric.Point} thisArg 4260 */ 4261 divideEquals: function (scalar) { 4262 this.x /= scalar; 4263 this.y /= scalar; 4264 return this; 4265 }, 4266 4267 /** 4268 * Returns true if this point is equal to another one 4269 * @param {fabric.Point} that 4270 * @return {Boolean} 4271 */ 4272 eq: function (that) { 4273 return (this.x === that.x && this.y === that.y); 4274 }, 4275 4276 /** 4277 * Returns true if this point is less than another one 4278 * @param {fabric.Point} that 4279 * @return {Boolean} 4280 */ 4281 lt: function (that) { 4282 return (this.x < that.x && this.y < that.y); 4283 }, 4284 4285 /** 4286 * Returns true if this point is less than or equal to another one 4287 * @param {fabric.Point} that 4288 * @return {Boolean} 4289 */ 4290 lte: function (that) { 4291 return (this.x <= that.x && this.y <= that.y); 4292 }, 4293 4294 /** 4295 4296 * Returns true if this point is greater another one 4297 * @param {fabric.Point} that 4298 * @return {Boolean} 4299 */ 4300 gt: function (that) { 4301 return (this.x > that.x && this.y > that.y); 4302 }, 4303 4304 /** 4305 * Returns true if this point is greater than or equal to another one 4306 * @param {fabric.Point} that 4307 * @return {Boolean} 4308 */ 4309 gte: function (that) { 4310 return (this.x >= that.x && this.y >= that.y); 4311 }, 4312 4313 /** 4314 * Returns new point which is the result of linear interpolation with this one and another one 4315 * @param {fabric.Point} that 4316 * @param {Number} t 4317 * @return {fabric.Point} 4318 */ 4319 lerp: function (that, t) { 4320 return new Point(this.x + (that.x - this.x) * t, this.y + (that.y - this.y) * t); 4321 }, 4322 4323 /** 4324 * Returns distance from this point and another one 4325 * @param {fabric.Point} that 4326 * @return {Number} 4327 */ 4328 distanceFrom: function (that) { 4329 var dx = this.x - that.x, 4330 dy = this.y - that.y; 4331 return Math.sqrt(dx * dx + dy * dy); 4332 }, 4333 4334 /** 4335 * Returns the point between this point and another one 4336 * @param {fabric.Point} that 4337 * @return {fabric.Point} 4338 */ 4339 midPointFrom: function (that) { 4340 return new Point(this.x + (that.x - this.x)/2, this.y + (that.y - this.y)/2); 4341 }, 4342 4343 /** 4344 * Returns a new point which is the min of this and another one 4345 * @param {fabric.Point} that 4346 * @return {fabric.Point} 4347 */ 4348 min: function (that) { 4349 return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y)); 4350 }, 4351 4352 /** 4353 * Returns a new point which is the max of this and another one 4354 * @param {fabric.Point} that 4355 * @return {fabric.Point} 4356 */ 4357 max: function (that) { 4358 return new Point(Math.max(this.x, that.x), Math.max(this.y, that.y)); 4359 }, 4360 4361 /** 4362 * Returns string representation of this point 4363 * @return {String} 4364 */ 4365 toString: function () { 4366 return this.x + ',' + this.y; 4367 }, 4368 4369 /** 4370 * Sets x/y of this point 4371 * @param {Number} x 4372 * @param {Number} y 4373 */ 4374 setXY: function (x, y) { 4375 this.x = x; 4376 this.y = y; 4377 }, 4378 4379 /** 4380 * Sets x/y of this point from another point 4381 * @param {fabric.Point} that 4382 */ 4383 setFromPoint: function (that) { 4384 this.x = that.x; 4385 this.y = that.y; 4386 }, 4387 4388 /** 4389 * Swaps x/y of this point and another point 4390 * @param {fabric.Point} that 4391 */ 4392 swap: function (that) { 4393 var x = this.x, 4394 y = this.y; 4395 this.x = that.x; 4396 this.y = that.y; 4397 that.x = x; 4398 that.y = y; 4399 } 4400 }; 4401 4402 })(typeof exports !== 'undefined' ? exports : this); 4403 4404 4405 (function(global) { 4406 4407 'use strict'; 4408 4409 /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ 4410 var fabric = global.fabric || (global.fabric = { }); 4411 4412 if (fabric.Intersection) { 4413 fabric.warn('fabric.Intersection is already defined'); 4414 return; 4415 } 4416 4417 /** 4418 * Intersection class 4419 * @class fabric.Intersection 4420 * @memberOf fabric 4421 * @constructor 4422 */ 4423 function Intersection(status) { 4424 this.status = status; 4425 this.points = []; 4426 } 4427 4428 fabric.Intersection = Intersection; 4429 4430 fabric.Intersection.prototype = /** @lends fabric.Intersection.prototype */ { 4431 4432 /** 4433 * Appends a point to intersection 4434 * @param {fabric.Point} point 4435 */ 4436 appendPoint: function (point) { 4437 this.points.push(point); 4438 }, 4439 4440 /** 4441 * Appends points to intersection 4442 * @param {Array} points 4443 */ 4444 appendPoints: function (points) { 4445 this.points = this.points.concat(points); 4446 } 4447 }; 4448 4449 /** 4450 * Checks if one line intersects another 4451 * @static 4452 * @param {fabric.Point} a1 4453 * @param {fabric.Point} a2 4454 * @param {fabric.Point} b1 4455 * @param {fabric.Point} b2 4456 * @return {fabric.Intersection} 4457 */ 4458 fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) { 4459 var result, 4460 uaT = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x), 4461 ubT = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x), 4462 uB = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y); 4463 if (uB !== 0) { 4464 var ua = uaT / uB, 4465 ub = ubT / uB; 4466 if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) { 4467 result = new Intersection('Intersection'); 4468 result.points.push(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y))); 4469 } 4470 else { 4471 result = new Intersection(); 4472 } 4473 } 4474 else { 4475 if (uaT === 0 || ubT === 0) { 4476 result = new Intersection('Coincident'); 4477 } 4478 else { 4479 result = new Intersection('Parallel'); 4480 } 4481 } 4482 return result; 4483 }; 4484 4485 /** 4486 * Checks if line intersects polygon 4487 * @static 4488 * @param {fabric.Point} a1 4489 * @param {fabric.Point} a2 4490 * @param {Array} points 4491 * @return {fabric.Intersection} 4492 */ 4493 fabric.Intersection.intersectLinePolygon = function(a1, a2, points) { 4494 var result = new Intersection(), 4495 length = points.length; 4496 4497 for (var i = 0; i < length; i++) { 4498 var b1 = points[i], 4499 b2 = points[(i + 1) % length], 4500 inter = Intersection.intersectLineLine(a1, a2, b1, b2); 4501 4502 result.appendPoints(inter.points); 4503 } 4504 if (result.points.length > 0) { 4505 result.status = 'Intersection'; 4506 } 4507 return result; 4508 }; 4509 4510 /** 4511 * Checks if polygon intersects another polygon 4512 * @static 4513 * @param {Array} points1 4514 * @param {Array} points2 4515 * @return {fabric.Intersection} 4516 */ 4517 fabric.Intersection.intersectPolygonPolygon = function (points1, points2) { 4518 var result = new Intersection(), 4519 length = points1.length; 4520 4521 for (var i = 0; i < length; i++) { 4522 var a1 = points1[i], 4523 a2 = points1[(i + 1) % length], 4524 inter = Intersection.intersectLinePolygon(a1, a2, points2); 4525 4526 result.appendPoints(inter.points); 4527 } 4528 if (result.points.length > 0) { 4529 result.status = 'Intersection'; 4530 } 4531 return result; 4532 }; 4533 4534 /** 4535 * Checks if polygon intersects rectangle 4536 * @static 4537 * @param {Array} points 4538 * @param {Number} r1 4539 * @param {Number} r2 4540 * @return {fabric.Intersection} 4541 */ 4542 fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) { 4543 var min = r1.min(r2), 4544 max = r1.max(r2), 4545 topRight = new fabric.Point(max.x, min.y), 4546 bottomLeft = new fabric.Point(min.x, max.y), 4547 inter1 = Intersection.intersectLinePolygon(min, topRight, points), 4548 inter2 = Intersection.intersectLinePolygon(topRight, max, points), 4549 inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points), 4550 inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points), 4551 result = new Intersection(); 4552 4553 result.appendPoints(inter1.points); 4554 result.appendPoints(inter2.points); 4555 result.appendPoints(inter3.points); 4556 result.appendPoints(inter4.points); 4557 4558 if (result.points.length > 0) { 4559 result.status = 'Intersection'; 4560 } 4561 return result; 4562 }; 4563 4564 })(typeof exports !== 'undefined' ? exports : this); 4565 4566 4567 (function(global) { 4568 4569 'use strict'; 4570 4571 var fabric = global.fabric || (global.fabric = { }); 4572 4573 if (fabric.Color) { 4574 fabric.warn('fabric.Color is already defined.'); 4575 return; 4576 } 4577 4578 /** 4579 * Color class 4580 * The purpose of {@link fabric.Color} is to abstract and encapsulate common color operations; 4581 * {@link fabric.Color} is a constructor and creates instances of {@link fabric.Color} objects. 4582 * 4583 * @class fabric.Color 4584 * @param {String} color optional in hex or rgb(a) format 4585 * @return {fabric.Color} thisArg 4586 * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#colors} 4587 */ 4588 function Color(color) { 4589 if (!color) { 4590 this.setSource([0, 0, 0, 1]); 4591 } 4592 else { 4593 this._tryParsingColor(color); 4594 } 4595 } 4596 4597 fabric.Color = Color; 4598 4599 fabric.Color.prototype = /** @lends fabric.Color.prototype */ { 4600 4601 /** 4602 * @private 4603 * @param {String|Array} color Color value to parse 4604 */ 4605 _tryParsingColor: function(color) { 4606 var source; 4607 4608 if (color in Color.colorNameMap) { 4609 color = Color.colorNameMap[color]; 4610 } 4611 4612 if (color === 'transparent') { 4613 this.setSource([255, 255, 255, 0]); 4614 return; 4615 } 4616 4617 source = Color.sourceFromHex(color); 4618 4619 if (!source) { 4620 source = Color.sourceFromRgb(color); 4621 } 4622 if (!source) { 4623 source = Color.sourceFromHsl(color); 4624 } 4625 if (source) { 4626 this.setSource(source); 4627 } 4628 }, 4629 4630 /** 4631 * Adapted from <a href="https://rawgithub.com/mjijackson/mjijackson.github.com/master/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript.html">https://github.com/mjijackson</a> 4632 * @private 4633 * @param {Number} r Red color value 4634 * @param {Number} g Green color value 4635 * @param {Number} b Blue color value 4636 * @return {Array} Hsl color 4637 */ 4638 _rgbToHsl: function(r, g, b) { 4639 r /= 255, g /= 255, b /= 255; 4640 4641 var h, s, l, 4642 max = fabric.util.array.max([r, g, b]), 4643 min = fabric.util.array.min([r, g, b]); 4644 4645 l = (max + min) / 2; 4646 4647 if (max === min) { 4648 h = s = 0; // achromatic 4649 } 4650 else { 4651 var d = max - min; 4652 s = l > 0.5 ? d / (2 - max - min) : d / (max + min); 4653 switch (max) { 4654 case r: 4655 h = (g - b) / d + (g < b ? 6 : 0); 4656 break; 4657 case g: 4658 h = (b - r) / d + 2; 4659 break; 4660 case b: 4661 h = (r - g) / d + 4; 4662 break; 4663 } 4664 h /= 6; 4665 } 4666 4667 return [ 4668 Math.round(h * 360), 4669 Math.round(s * 100), 4670 Math.round(l * 100) 4671 ]; 4672 }, 4673 4674 /** 4675 * Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1]) 4676 * @return {Array} 4677 */ 4678 getSource: function() { 4679 return this._source; 4680 }, 4681 4682 /** 4683 * Sets source of this color (where source is an array representation; ex: [200, 200, 100, 1]) 4684 * @param {Array} source 4685 */ 4686 setSource: function(source) { 4687 this._source = source; 4688 }, 4689 4690 /** 4691 * Returns color represenation in RGB format 4692 * @return {String} ex: rgb(0-255,0-255,0-255) 4693 */ 4694 toRgb: function() { 4695 var source = this.getSource(); 4696 return 'rgb(' + source[0] + ',' + source[1] + ',' + source[2] + ')'; 4697 }, 4698 4699 /** 4700 * Returns color represenation in RGBA format 4701 * @return {String} ex: rgba(0-255,0-255,0-255,0-1) 4702 */ 4703 toRgba: function() { 4704 var source = this.getSource(); 4705 return 'rgba(' + source[0] + ',' + source[1] + ',' + source[2] + ',' + source[3] + ')'; 4706 }, 4707 4708 /** 4709 * Returns color represenation in HSL format 4710 * @return {String} ex: hsl(0-360,0%-100%,0%-100%) 4711 */ 4712 toHsl: function() { 4713 var source = this.getSource(), 4714 hsl = this._rgbToHsl(source[0], source[1], source[2]); 4715 4716 return 'hsl(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%)'; 4717 }, 4718 4719 /** 4720 * Returns color represenation in HSLA format 4721 * @return {String} ex: hsla(0-360,0%-100%,0%-100%,0-1) 4722 */ 4723 toHsla: function() { 4724 var source = this.getSource(), 4725 hsl = this._rgbToHsl(source[0], source[1], source[2]); 4726 4727 return 'hsla(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%,' + source[3] + ')'; 4728 }, 4729 4730 /** 4731 * Returns color represenation in HEX format 4732 * @return {String} ex: FF5555 4733 */ 4734 toHex: function() { 4735 var source = this.getSource(), r, g, b; 4736 4737 r = source[0].toString(16); 4738 r = (r.length === 1) ? ('0' + r) : r; 4739 4740 g = source[1].toString(16); 4741 g = (g.length === 1) ? ('0' + g) : g; 4742 4743 b = source[2].toString(16); 4744 b = (b.length === 1) ? ('0' + b) : b; 4745 4746 return r.toUpperCase() + g.toUpperCase() + b.toUpperCase(); 4747 }, 4748 4749 /** 4750 * Gets value of alpha channel for this color 4751 * @return {Number} 0-1 4752 */ 4753 getAlpha: function() { 4754 return this.getSource()[3]; 4755 }, 4756 4757 /** 4758 * Sets value of alpha channel for this color 4759 * @param {Number} alpha Alpha value 0-1 4760 * @return {fabric.Color} thisArg 4761 */ 4762 setAlpha: function(alpha) { 4763 var source = this.getSource(); 4764 source[3] = alpha; 4765 this.setSource(source); 4766 return this; 4767 }, 4768 4769 /** 4770 * Transforms color to its grayscale representation 4771 * @return {fabric.Color} thisArg 4772 */ 4773 toGrayscale: function() { 4774 var source = this.getSource(), 4775 average = parseInt((source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), 10), 4776 currentAlpha = source[3]; 4777 this.setSource([average, average, average, currentAlpha]); 4778 return this; 4779 }, 4780 4781 /** 4782 * Transforms color to its black and white representation 4783 * @param {Number} threshold 4784 * @return {fabric.Color} thisArg 4785 */ 4786 toBlackWhite: function(threshold) { 4787 var source = this.getSource(), 4788 average = (source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), 4789 currentAlpha = source[3]; 4790 4791 threshold = threshold || 127; 4792 4793 average = (Number(average) < Number(threshold)) ? 0 : 255; 4794 this.setSource([average, average, average, currentAlpha]); 4795 return this; 4796 }, 4797 4798 /** 4799 * Overlays color with another color 4800 * @param {String|fabric.Color} otherColor 4801 * @return {fabric.Color} thisArg 4802 */ 4803 overlayWith: function(otherColor) { 4804 if (!(otherColor instanceof Color)) { 4805 otherColor = new Color(otherColor); 4806 } 4807 4808 var result = [], 4809 alpha = this.getAlpha(), 4810 otherAlpha = 0.5, 4811 source = this.getSource(), 4812 otherSource = otherColor.getSource(); 4813 4814 for (var i = 0; i < 3; i++) { 4815 result.push(Math.round((source[i] * (1 - otherAlpha)) + (otherSource[i] * otherAlpha))); 4816 } 4817 4818 result[3] = alpha; 4819 this.setSource(result); 4820 return this; 4821 } 4822 }; 4823 4824 /** 4825 * Regex matching color in RGB or RGBA formats (ex: rgb(0, 0, 0), rgba(255, 100, 10, 0.5), rgba( 255 , 100 , 10 , 0.5 ), rgb(1,1,1), rgba(100%, 60%, 10%, 0.5)) 4826 * @static 4827 * @field 4828 * @memberOf fabric.Color 4829 */ 4830 fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/; 4831 4832 /** 4833 * Regex matching color in HSL or HSLA formats (ex: hsl(200, 80%, 10%), hsla(300, 50%, 80%, 0.5), hsla( 300 , 50% , 80% , 0.5 )) 4834 * @static 4835 * @field 4836 * @memberOf fabric.Color 4837 */ 4838 fabric.Color.reHSLa = /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/; 4839 4840 /** 4841 * Regex matching color in HEX format (ex: #FF5555, 010155, aff) 4842 * @static 4843 * @field 4844 * @memberOf fabric.Color 4845 */ 4846 fabric.Color.reHex = /^#?([0-9a-f]{6}|[0-9a-f]{3})$/i; 4847 4848 /** 4849 * Map of the 17 basic color names with HEX code 4850 * @static 4851 * @field 4852 * @memberOf fabric.Color 4853 * @see: http://www.w3.org/TR/CSS2/syndata.html#color-units 4854 */ 4855 fabric.Color.colorNameMap = { 4856 aqua: '#00FFFF', 4857 black: '#000000', 4858 blue: '#0000FF', 4859 fuchsia: '#FF00FF', 4860 gray: '#808080', 4861 green: '#008000', 4862 lime: '#00FF00', 4863 maroon: '#800000', 4864 navy: '#000080', 4865 olive: '#808000', 4866 orange: '#FFA500', 4867 purple: '#800080', 4868 red: '#FF0000', 4869 silver: '#C0C0C0', 4870 teal: '#008080', 4871 white: '#FFFFFF', 4872 yellow: '#FFFF00' 4873 }; 4874 4875 /** 4876 * @private 4877 * @param {Number} p 4878 * @param {Number} q 4879 * @param {Number} t 4880 * @return {Number} 4881 */ 4882 function hue2rgb(p, q, t) { 4883 if (t < 0) { 4884 t += 1; 4885 } 4886 if (t > 1) { 4887 t -= 1; 4888 } 4889 if (t < 1/6) { 4890 return p + (q - p) * 6 * t; 4891 } 4892 if (t < 1/2) { 4893 return q; 4894 } 4895 if (t < 2/3) { 4896 return p + (q - p) * (2/3 - t) * 6; 4897 } 4898 return p; 4899 } 4900 4901 /** 4902 * Returns new color object, when given a color in RGB format 4903 * @memberOf fabric.Color 4904 * @param {String} color Color value ex: rgb(0-255,0-255,0-255) 4905 * @return {fabric.Color} 4906 */ 4907 fabric.Color.fromRgb = function(color) { 4908 return Color.fromSource(Color.sourceFromRgb(color)); 4909 }; 4910 4911 /** 4912 * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in RGB or RGBA format 4913 * @memberOf fabric.Color 4914 * @param {String} color Color value ex: rgb(0-255,0-255,0-255), rgb(0%-100%,0%-100%,0%-100%) 4915 * @return {Array} source 4916 */ 4917 fabric.Color.sourceFromRgb = function(color) { 4918 var match = color.match(Color.reRGBa); 4919 if (match) { 4920 var r = parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1) * (/%$/.test(match[1]) ? 255 : 1), 4921 g = parseInt(match[2], 10) / (/%$/.test(match[2]) ? 100 : 1) * (/%$/.test(match[2]) ? 255 : 1), 4922 b = parseInt(match[3], 10) / (/%$/.test(match[3]) ? 100 : 1) * (/%$/.test(match[3]) ? 255 : 1); 4923 4924 return [ 4925 parseInt(r, 10), 4926 parseInt(g, 10), 4927 parseInt(b, 10), 4928 match[4] ? parseFloat(match[4]) : 1 4929 ]; 4930 } 4931 }; 4932 4933 /** 4934 * Returns new color object, when given a color in RGBA format 4935 * @static 4936 * @function 4937 * @memberOf fabric.Color 4938 * @param {String} color 4939 * @return {fabric.Color} 4940 */ 4941 fabric.Color.fromRgba = Color.fromRgb; 4942 4943 /** 4944 * Returns new color object, when given a color in HSL format 4945 * @param {String} color Color value ex: hsl(0-260,0%-100%,0%-100%) 4946 * @memberOf fabric.Color 4947 * @return {fabric.Color} 4948 */ 4949 fabric.Color.fromHsl = function(color) { 4950 return Color.fromSource(Color.sourceFromHsl(color)); 4951 }; 4952 4953 /** 4954 * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in HSL or HSLA format. 4955 * Adapted from <a href="https://rawgithub.com/mjijackson/mjijackson.github.com/master/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript.html">https://github.com/mjijackson</a> 4956 * @memberOf fabric.Color 4957 * @param {String} color Color value ex: hsl(0-360,0%-100%,0%-100%) or hsla(0-360,0%-100%,0%-100%, 0-1) 4958 * @return {Array} source 4959 * @see http://http://www.w3.org/TR/css3-color/#hsl-color 4960 */ 4961 fabric.Color.sourceFromHsl = function(color) { 4962 var match = color.match(Color.reHSLa); 4963 if (!match) { 4964 return; 4965 } 4966 4967 var h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360, 4968 s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1), 4969 l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1), 4970 r, g, b; 4971 4972 if (s === 0) { 4973 r = g = b = l; 4974 } 4975 else { 4976 var q = l <= 0.5 ? l * (s + 1) : l + s - l * s, 4977 p = l * 2 - q; 4978 4979 r = hue2rgb(p, q, h + 1/3); 4980 g = hue2rgb(p, q, h); 4981 b = hue2rgb(p, q, h - 1/3); 4982 } 4983 4984 return [ 4985 Math.round(r * 255), 4986 Math.round(g * 255), 4987 Math.round(b * 255), 4988 match[4] ? parseFloat(match[4]) : 1 4989 ]; 4990 }; 4991 4992 /** 4993 * Returns new color object, when given a color in HSLA format 4994 * @static 4995 * @function 4996 * @memberOf fabric.Color 4997 * @param {String} color 4998 * @return {fabric.Color} 4999 */ 5000 fabric.Color.fromHsla = Color.fromHsl; 5001 5002 /** 5003 * Returns new color object, when given a color in HEX format 5004 * @static 5005 * @memberOf fabric.Color 5006 * @param {String} color Color value ex: FF5555 5007 * @return {fabric.Color} 5008 */ 5009 fabric.Color.fromHex = function(color) { 5010 return Color.fromSource(Color.sourceFromHex(color)); 5011 }; 5012 5013 /** 5014 * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in HEX format 5015 * @static 5016 * @memberOf fabric.Color 5017 * @param {String} color ex: FF5555 5018 * @return {Array} source 5019 */ 5020 fabric.Color.sourceFromHex = function(color) { 5021 if (color.match(Color.reHex)) { 5022 var value = color.slice(color.indexOf('#') + 1), 5023 isShortNotation = (value.length === 3), 5024 r = isShortNotation ? (value.charAt(0) + value.charAt(0)) : value.substring(0, 2), 5025 g = isShortNotation ? (value.charAt(1) + value.charAt(1)) : value.substring(2, 4), 5026 b = isShortNotation ? (value.charAt(2) + value.charAt(2)) : value.substring(4, 6); 5027 5028 return [ 5029 parseInt(r, 16), 5030 parseInt(g, 16), 5031 parseInt(b, 16), 5032 1 5033 ]; 5034 } 5035 }; 5036 5037 /** 5038 * Returns new color object, when given color in array representation (ex: [200, 100, 100, 0.5]) 5039 * @static 5040 * @memberOf fabric.Color 5041 * @param {Array} source 5042 * @return {fabric.Color} 5043 */ 5044 fabric.Color.fromSource = function(source) { 5045 var oColor = new Color(); 5046 oColor.setSource(source); 5047 return oColor; 5048 }; 5049 5050 })(typeof exports !== 'undefined' ? exports : this); 5051 5052 5053 (function() { 5054 5055 /* _FROM_SVG_START_ */ 5056 function getColorStop(el) { 5057 var style = el.getAttribute('style'), 5058 offset = el.getAttribute('offset') || 0, 5059 color, colorAlpha, opacity; 5060 5061 // convert percents to absolute values 5062 offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1); 5063 offset = offset < 0 ? 0 : offset > 1 ? 1 : offset; 5064 if (style) { 5065 var keyValuePairs = style.split(/\s*;\s*/); 5066 5067 if (keyValuePairs[keyValuePairs.length - 1] === '') { 5068 keyValuePairs.pop(); 5069 } 5070 5071 for (var i = keyValuePairs.length; i--; ) { 5072 5073 var split = keyValuePairs[i].split(/\s*:\s*/), 5074 key = split[0].trim(), 5075 value = split[1].trim(); 5076 5077 if (key === 'stop-color') { 5078 color = value; 5079 } 5080 else if (key === 'stop-opacity') { 5081 opacity = value; 5082 } 5083 } 5084 } 5085 5086 if (!color) { 5087 color = el.getAttribute('stop-color') || 'rgb(0,0,0)'; 5088 } 5089 if (!opacity) { 5090 opacity = el.getAttribute('stop-opacity'); 5091 } 5092 5093 color = new fabric.Color(color); 5094 colorAlpha = color.getAlpha(); 5095 opacity = isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity); 5096 opacity *= colorAlpha; 5097 5098 return { 5099 offset: offset, 5100 color: color.toRgb(), 5101 opacity: opacity 5102 }; 5103 } 5104 5105 function getLinearCoords(el) { 5106 return { 5107 x1: el.getAttribute('x1') || 0, 5108 y1: el.getAttribute('y1') || 0, 5109 x2: el.getAttribute('x2') || '100%', 5110 y2: el.getAttribute('y2') || 0 5111 }; 5112 } 5113 5114 function getRadialCoords(el) { 5115 return { 5116 x1: el.getAttribute('fx') || el.getAttribute('cx') || '50%', 5117 y1: el.getAttribute('fy') || el.getAttribute('cy') || '50%', 5118 r1: 0, 5119 x2: el.getAttribute('cx') || '50%', 5120 y2: el.getAttribute('cy') || '50%', 5121 r2: el.getAttribute('r') || '50%' 5122 }; 5123 } 5124 /* _FROM_SVG_END_ */ 5125 5126 /** 5127 * Gradient class 5128 * @class fabric.Gradient 5129 * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#gradients} 5130 * @see {@link fabric.Gradient#initialize} for constructor definition 5131 */ 5132 fabric.Gradient = fabric.util.createClass(/** @lends fabric.Gradient.prototype */ { 5133 5134 /** 5135 * Horizontal offset for aligning gradients coming from SVG when outside pathgroups 5136 * @type Number 5137 * @default 0 5138 */ 5139 offsetX: 0, 5140 5141 /** 5142 * Vertical offset for aligning gradients coming from SVG when outside pathgroups 5143 * @type Number 5144 * @default 0 5145 */ 5146 offsetY: 0, 5147 5148 /** 5149 * Constructor 5150 * @param {Object} [options] Options object with type, coords, gradientUnits and colorStops 5151 * @return {fabric.Gradient} thisArg 5152 */ 5153 initialize: function(options) { 5154 options || (options = { }); 5155 5156 var coords = { }; 5157 5158 this.id = fabric.Object.__uid++; 5159 this.type = options.type || 'linear'; 5160 5161 coords = { 5162 x1: options.coords.x1 || 0, 5163 y1: options.coords.y1 || 0, 5164 x2: options.coords.x2 || 0, 5165 y2: options.coords.y2 || 0 5166 }; 5167 5168 if (this.type === 'radial') { 5169 coords.r1 = options.coords.r1 || 0; 5170 coords.r2 = options.coords.r2 || 0; 5171 } 5172 this.coords = coords; 5173 this.colorStops = options.colorStops.slice(); 5174 if (options.gradientTransform) { 5175 this.gradientTransform = options.gradientTransform; 5176 } 5177 this.offsetX = options.offsetX || this.offsetX; 5178 this.offsetY = options.offsetY || this.offsetY; 5179 }, 5180 5181 /** 5182 * Adds another colorStop 5183 * @param {Object} colorStop Object with offset and color 5184 * @return {fabric.Gradient} thisArg 5185 */ 5186 addColorStop: function(colorStop) { 5187 for (var position in colorStop) { 5188 var color = new fabric.Color(colorStop[position]); 5189 this.colorStops.push({ 5190 offset: position, 5191 color: color.toRgb(), 5192 opacity: color.getAlpha() 5193 }); 5194 } 5195 return this; 5196 }, 5197 5198 /** 5199 * Returns object representation of a gradient 5200 * @return {Object} 5201 */ 5202 toObject: function() { 5203 return { 5204 type: this.type, 5205 coords: this.coords, 5206 colorStops: this.colorStops, 5207 offsetX: this.offsetX, 5208 offsetY: this.offsetY, 5209 gradientTransform: this.gradientTransform ? this.gradientTransform.concat() : this.gradientTransform 5210 }; 5211 }, 5212 5213 /* _TO_SVG_START_ */ 5214 /** 5215 * Returns SVG representation of an gradient 5216 * @param {Object} object Object to create a gradient for 5217 * @param {Boolean} normalize Whether coords should be normalized 5218 * @return {String} SVG representation of an gradient (linear/radial) 5219 */ 5220 toSVG: function(object) { 5221 var coords = fabric.util.object.clone(this.coords), 5222 markup, commonAttributes; 5223 5224 // colorStops must be sorted ascending 5225 this.colorStops.sort(function(a, b) { 5226 return a.offset - b.offset; 5227 }); 5228 5229 if (!(object.group && object.group.type === 'path-group')) { 5230 for (var prop in coords) { 5231 if (prop === 'x1' || prop === 'x2' || prop === 'r2') { 5232 coords[prop] += this.offsetX - object.width / 2; 5233 } 5234 else if (prop === 'y1' || prop === 'y2') { 5235 coords[prop] += this.offsetY - object.height / 2; 5236 } 5237 } 5238 } 5239 5240 commonAttributes = 'id="SVGID_' + this.id + 5241 '" gradientUnits="userSpaceOnUse"'; 5242 if (this.gradientTransform) { 5243 commonAttributes += ' gradientTransform="matrix(' + this.gradientTransform.join(' ') + ')" '; 5244 } 5245 if (this.type === 'linear') { 5246 markup = [ 5247 //jscs:disable validateIndentation 5248 '<linearGradient ', 5249 commonAttributes, 5250 ' x1="', coords.x1, 5251 '" y1="', coords.y1, 5252 '" x2="', coords.x2, 5253 '" y2="', coords.y2, 5254 '">\n' 5255 //jscs:enable validateIndentation 5256 ]; 5257 } 5258 else if (this.type === 'radial') { 5259 markup = [ 5260 //jscs:disable validateIndentation 5261 '<radialGradient ', 5262 commonAttributes, 5263 ' cx="', coords.x2, 5264 '" cy="', coords.y2, 5265 '" r="', coords.r2, 5266 '" fx="', coords.x1, 5267 '" fy="', coords.y1, 5268 '">\n' 5269 //jscs:enable validateIndentation 5270 ]; 5271 } 5272 5273 for (var i = 0; i < this.colorStops.length; i++) { 5274 markup.push( 5275 //jscs:disable validateIndentation 5276 '<stop ', 5277 'offset="', (this.colorStops[i].offset * 100) + '%', 5278 '" style="stop-color:', this.colorStops[i].color, 5279 (this.colorStops[i].opacity != null ? ';stop-opacity: ' + this.colorStops[i].opacity : ';'), 5280 '"/>\n' 5281 //jscs:enable validateIndentation 5282 ); 5283 } 5284 5285 markup.push((this.type === 'linear' ? '</linearGradient>\n' : '</radialGradient>\n')); 5286 5287 return markup.join(''); 5288 }, 5289 /* _TO_SVG_END_ */ 5290 5291 /** 5292 * Returns an instance of CanvasGradient 5293 * @param {CanvasRenderingContext2D} ctx Context to render on 5294 * @return {CanvasGradient} 5295 */ 5296 toLive: function(ctx, object) { 5297 var gradient, prop, coords = fabric.util.object.clone(this.coords); 5298 5299 if (!this.type) { 5300 return; 5301 } 5302 5303 if (object.group && object.group.type === 'path-group') { 5304 for (prop in coords) { 5305 if (prop === 'x1' || prop === 'x2') { 5306 coords[prop] += -this.offsetX + object.width / 2; 5307 } 5308 else if (prop === 'y1' || prop === 'y2') { 5309 coords[prop] += -this.offsetY + object.height / 2; 5310 } 5311 } 5312 } 5313 5314 if (this.type === 'linear') { 5315 gradient = ctx.createLinearGradient( 5316 coords.x1, coords.y1, coords.x2, coords.y2); 5317 } 5318 else if (this.type === 'radial') { 5319 gradient = ctx.createRadialGradient( 5320 coords.x1, coords.y1, coords.r1, coords.x2, coords.y2, coords.r2); 5321 } 5322 5323 for (var i = 0, len = this.colorStops.length; i < len; i++) { 5324 var color = this.colorStops[i].color, 5325 opacity = this.colorStops[i].opacity, 5326 offset = this.colorStops[i].offset; 5327 5328 if (typeof opacity !== 'undefined') { 5329 color = new fabric.Color(color).setAlpha(opacity).toRgba(); 5330 } 5331 gradient.addColorStop(parseFloat(offset), color); 5332 } 5333 5334 return gradient; 5335 } 5336 }); 5337 5338 fabric.util.object.extend(fabric.Gradient, { 5339 5340 /* _FROM_SVG_START_ */ 5341 /** 5342 * Returns {@link fabric.Gradient} instance from an SVG element 5343 * @static 5344 * @memberOf fabric.Gradient 5345 * @param {SVGGradientElement} el SVG gradient element 5346 * @param {fabric.Object} instance 5347 * @return {fabric.Gradient} Gradient instance 5348 * @see http://www.w3.org/TR/SVG/pservers.html#LinearGradientElement 5349 * @see http://www.w3.org/TR/SVG/pservers.html#RadialGradientElement 5350 */ 5351 fromElement: function(el, instance) { 5352 5353 /** 5354 * @example: 5355 * 5356 * <linearGradient id="linearGrad1"> 5357 * <stop offset="0%" stop-color="white"/> 5358 * <stop offset="100%" stop-color="black"/> 5359 * </linearGradient> 5360 * 5361 * OR 5362 * 5363 * <linearGradient id="linearGrad2"> 5364 * <stop offset="0" style="stop-color:rgb(255,255,255)"/> 5365 * <stop offset="1" style="stop-color:rgb(0,0,0)"/> 5366 * </linearGradient> 5367 * 5368 * OR 5369 * 5370 * <radialGradient id="radialGrad1"> 5371 * <stop offset="0%" stop-color="white" stop-opacity="1" /> 5372 * <stop offset="50%" stop-color="black" stop-opacity="0.5" /> 5373 * <stop offset="100%" stop-color="white" stop-opacity="1" /> 5374 * </radialGradient> 5375 * 5376 * OR 5377 * 5378 * <radialGradient id="radialGrad2"> 5379 * <stop offset="0" stop-color="rgb(255,255,255)" /> 5380 * <stop offset="0.5" stop-color="rgb(0,0,0)" /> 5381 * <stop offset="1" stop-color="rgb(255,255,255)" /> 5382 * </radialGradient> 5383 * 5384 */ 5385 5386 var colorStopEls = el.getElementsByTagName('stop'), 5387 type = (el.nodeName === 'linearGradient' ? 'linear' : 'radial'), 5388 gradientUnits = el.getAttribute('gradientUnits') || 'objectBoundingBox', 5389 gradientTransform = el.getAttribute('gradientTransform'), 5390 colorStops = [], 5391 coords = { }, ellipseMatrix; 5392 5393 if (type === 'linear') { 5394 coords = getLinearCoords(el); 5395 } 5396 else if (type === 'radial') { 5397 coords = getRadialCoords(el); 5398 } 5399 5400 for (var i = colorStopEls.length; i--; ) { 5401 colorStops.push(getColorStop(colorStopEls[i])); 5402 } 5403 5404 ellipseMatrix = _convertPercentUnitsToValues(instance, coords, gradientUnits); 5405 5406 var gradient = new fabric.Gradient({ 5407 type: type, 5408 coords: coords, 5409 colorStops: colorStops, 5410 offsetX: -instance.left, 5411 offsetY: -instance.top 5412 }); 5413 5414 if (gradientTransform || ellipseMatrix !== '') { 5415 gradient.gradientTransform = fabric.parseTransformAttribute((gradientTransform || '') + ellipseMatrix); 5416 } 5417 return gradient; 5418 }, 5419 /* _FROM_SVG_END_ */ 5420 5421 /** 5422 * Returns {@link fabric.Gradient} instance from its object representation 5423 * @static 5424 * @memberOf fabric.Gradient 5425 * @param {Object} obj 5426 * @param {Object} [options] Options object 5427 */ 5428 forObject: function(obj, options) { 5429 options || (options = { }); 5430 _convertPercentUnitsToValues(obj, options.coords, 'userSpaceOnUse'); 5431 return new fabric.Gradient(options); 5432 } 5433 }); 5434 5435 /** 5436 * @private 5437 */ 5438 function _convertPercentUnitsToValues(object, options, gradientUnits) { 5439 var propValue, addFactor = 0, multFactor = 1, ellipseMatrix = ''; 5440 for (var prop in options) { 5441 propValue = parseFloat(options[prop], 10); 5442 if (typeof options[prop] === 'string' && /^\d+%$/.test(options[prop])) { 5443 multFactor = 0.01; 5444 } 5445 else { 5446 multFactor = 1; 5447 } 5448 if (prop === 'x1' || prop === 'x2' || prop === 'r2') { 5449 multFactor *= gradientUnits === 'objectBoundingBox' ? object.width : 1; 5450 addFactor = gradientUnits === 'objectBoundingBox' ? object.left || 0 : 0; 5451 } 5452 else if (prop === 'y1' || prop === 'y2') { 5453 multFactor *= gradientUnits === 'objectBoundingBox' ? object.height : 1; 5454 addFactor = gradientUnits === 'objectBoundingBox' ? object.top || 0 : 0; 5455 } 5456 options[prop] = propValue * multFactor + addFactor; 5457 } 5458 if (object.type === 'ellipse' && 5459 options.r2 !== null && 5460 gradientUnits === 'objectBoundingBox' && 5461 object.rx !== object.ry) { 5462 5463 var scaleFactor = object.ry/object.rx; 5464 ellipseMatrix = ' scale(1, ' + scaleFactor + ')'; 5465 if (options.y1) { 5466 options.y1 /= scaleFactor; 5467 } 5468 if (options.y2) { 5469 options.y2 /= scaleFactor; 5470 } 5471 } 5472 return ellipseMatrix; 5473 } 5474 })(); 5475 5476 5477 /** 5478 * Pattern class 5479 * @class fabric.Pattern 5480 * @see {@link http://fabricjs.com/patterns/|Pattern demo} 5481 * @see {@link http://fabricjs.com/dynamic-patterns/|DynamicPattern demo} 5482 * @see {@link fabric.Pattern#initialize} for constructor definition 5483 */ 5484 fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ { 5485 5486 /** 5487 * Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat) 5488 * @type String 5489 * @default 5490 */ 5491 repeat: 'repeat', 5492 5493 /** 5494 * Pattern horizontal offset from object's left/top corner 5495 * @type Number 5496 * @default 5497 */ 5498 offsetX: 0, 5499 5500 /** 5501 * Pattern vertical offset from object's left/top corner 5502 * @type Number 5503 * @default 5504 */ 5505 offsetY: 0, 5506 5507 /** 5508 * Constructor 5509 * @param {Object} [options] Options object 5510 * @return {fabric.Pattern} thisArg 5511 */ 5512 initialize: function(options) { 5513 options || (options = { }); 5514 5515 this.id = fabric.Object.__uid++; 5516 5517 if (options.source) { 5518 if (typeof options.source === 'string') { 5519 // function string 5520 if (typeof fabric.util.getFunctionBody(options.source) !== 'undefined') { 5521 this.source = new Function(fabric.util.getFunctionBody(options.source)); 5522 } 5523 else { 5524 // img src string 5525 var _this = this; 5526 this.source = fabric.util.createImage(); 5527 fabric.util.loadImage(options.source, function(img) { 5528 _this.source = img; 5529 }); 5530 } 5531 } 5532 else { 5533 // img element 5534 this.source = options.source; 5535 } 5536 } 5537 if (options.repeat) { 5538 this.repeat = options.repeat; 5539 } 5540 if (options.offsetX) { 5541 this.offsetX = options.offsetX; 5542 } 5543 if (options.offsetY) { 5544 this.offsetY = options.offsetY; 5545 } 5546 }, 5547 5548 /** 5549 * Returns object representation of a pattern 5550 * @return {Object} Object representation of a pattern instance 5551 */ 5552 toObject: function() { 5553 5554 var source; 5555 5556 // callback 5557 if (typeof this.source === 'function') { 5558 source = String(this.source); 5559 } 5560 // <img> element 5561 else if (typeof this.source.src === 'string') { 5562 source = this.source.src; 5563 } 5564 // <canvas> element 5565 else if (typeof this.source === 'object' && this.source.toDataURL) { 5566 source = this.source.toDataURL(); 5567 } 5568 5569 return { 5570 source: source, 5571 repeat: this.repeat, 5572 offsetX: this.offsetX, 5573 offsetY: this.offsetY 5574 }; 5575 }, 5576 5577 /* _TO_SVG_START_ */ 5578 /** 5579 * Returns SVG representation of a pattern 5580 * @param {fabric.Object} object 5581 * @return {String} SVG representation of a pattern 5582 */ 5583 toSVG: function(object) { 5584 var patternSource = typeof this.source === 'function' ? this.source() : this.source, 5585 patternWidth = patternSource.width / object.getWidth(), 5586 patternHeight = patternSource.height / object.getHeight(), 5587 patternOffsetX = this.offsetX / object.getWidth(), 5588 patternOffsetY = this.offsetY / object.getHeight(), 5589 patternImgSrc = ''; 5590 if (this.repeat === 'repeat-x' || this.repeat === 'no-repeat') { 5591 patternHeight = 1; 5592 } 5593 if (this.repeat === 'repeat-y' || this.repeat === 'no-repeat') { 5594 patternWidth = 1; 5595 } 5596 if (patternSource.src) { 5597 patternImgSrc = patternSource.src; 5598 } 5599 else if (patternSource.toDataURL) { 5600 patternImgSrc = patternSource.toDataURL(); 5601 } 5602 5603 return '<pattern id="SVGID_' + this.id + 5604 '" x="' + patternOffsetX + 5605 '" y="' + patternOffsetY + 5606 '" width="' + patternWidth + 5607 '" height="' + patternHeight + '">\n' + 5608 '<image x="0" y="0"' + 5609 ' width="' + patternSource.width + 5610 '" height="' + patternSource.height + 5611 '" xlink:href="' + patternImgSrc + 5612 '"></image>\n' + 5613 '</pattern>\n'; 5614 }, 5615 /* _TO_SVG_END_ */ 5616 5617 /** 5618 * Returns an instance of CanvasPattern 5619 * @param {CanvasRenderingContext2D} ctx Context to create pattern 5620 * @return {CanvasPattern} 5621 */ 5622 toLive: function(ctx) { 5623 var source = typeof this.source === 'function' 5624 ? this.source() 5625 : this.source; 5626 5627 // if the image failed to load, return, and allow rest to continue loading 5628 if (!source) { 5629 return ''; 5630 } 5631 5632 // if an image 5633 if (typeof source.src !== 'undefined') { 5634 if (!source.complete) { 5635 return ''; 5636 } 5637 if (source.naturalWidth === 0 || source.naturalHeight === 0) { 5638 return ''; 5639 } 5640 } 5641 return ctx.createPattern(source, this.repeat); 5642 } 5643 }); 5644 5645 5646 (function(global) { 5647 5648 'use strict'; 5649 5650 var fabric = global.fabric || (global.fabric = { }), 5651 toFixed = fabric.util.toFixed; 5652 5653 if (fabric.Shadow) { 5654 fabric.warn('fabric.Shadow is already defined.'); 5655 return; 5656 } 5657 5658 /** 5659 * Shadow class 5660 * @class fabric.Shadow 5661 * @see {@link http://fabricjs.com/shadows/|Shadow demo} 5662 * @see {@link fabric.Shadow#initialize} for constructor definition 5663 */ 5664 fabric.Shadow = fabric.util.createClass(/** @lends fabric.Shadow.prototype */ { 5665 5666 /** 5667 * Shadow color 5668 * @type String 5669 * @default 5670 */ 5671 color: 'rgb(0,0,0)', 5672 5673 /** 5674 * Shadow blur 5675 * @type Number 5676 */ 5677 blur: 0, 5678 5679 /** 5680 * Shadow horizontal offset 5681 * @type Number 5682 * @default 5683 */ 5684 offsetX: 0, 5685 5686 /** 5687 * Shadow vertical offset 5688 * @type Number 5689 * @default 5690 */ 5691 offsetY: 0, 5692 5693 /** 5694 * Whether the shadow should affect stroke operations 5695 * @type Boolean 5696 * @default 5697 */ 5698 affectStroke: false, 5699 5700 /** 5701 * Indicates whether toObject should include default values 5702 * @type Boolean 5703 * @default 5704 */ 5705 includeDefaultValues: true, 5706 5707 /** 5708 * Constructor 5709 * @param {Object|String} [options] Options object with any of color, blur, offsetX, offsetX properties or string (e.g. "rgba(0,0,0,0.2) 2px 2px 10px, "2px 2px 10px rgba(0,0,0,0.2)") 5710 * @return {fabric.Shadow} thisArg 5711 */ 5712 initialize: function(options) { 5713 5714 if (typeof options === 'string') { 5715 options = this._parseShadow(options); 5716 } 5717 5718 for (var prop in options) { 5719 this[prop] = options[prop]; 5720 } 5721 5722 this.id = fabric.Object.__uid++; 5723 }, 5724 5725 /** 5726 * @private 5727 * @param {String} shadow Shadow value to parse 5728 * @return {Object} Shadow object with color, offsetX, offsetY and blur 5729 */ 5730 _parseShadow: function(shadow) { 5731 var shadowStr = shadow.trim(), 5732 offsetsAndBlur = fabric.Shadow.reOffsetsAndBlur.exec(shadowStr) || [ ], 5733 color = shadowStr.replace(fabric.Shadow.reOffsetsAndBlur, '') || 'rgb(0,0,0)'; 5734 5735 return { 5736 color: color.trim(), 5737 offsetX: parseInt(offsetsAndBlur[1], 10) || 0, 5738 offsetY: parseInt(offsetsAndBlur[2], 10) || 0, 5739 blur: parseInt(offsetsAndBlur[3], 10) || 0 5740 }; 5741 }, 5742 5743 /** 5744 * Returns a string representation of an instance 5745 * @see http://www.w3.org/TR/css-text-decor-3/#text-shadow 5746 * @return {String} Returns CSS3 text-shadow declaration 5747 */ 5748 toString: function() { 5749 return [this.offsetX, this.offsetY, this.blur, this.color].join('px '); 5750 }, 5751 5752 /* _TO_SVG_START_ */ 5753 /** 5754 * Returns SVG representation of a shadow 5755 * @param {fabric.Object} object 5756 * @return {String} SVG representation of a shadow 5757 */ 5758 toSVG: function(object) { 5759 var fBoxX = 40, fBoxY = 40, NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, 5760 offset = fabric.util.rotateVector( 5761 { x: this.offsetX, y: this.offsetY }, 5762 fabric.util.degreesToRadians(-object.angle)), 5763 BLUR_BOX = 20; 5764 5765 if (object.width && object.height) { 5766 //http://www.w3.org/TR/SVG/filters.html#FilterEffectsRegion 5767 // we add some extra space to filter box to contain the blur ( 20 ) 5768 fBoxX = toFixed((Math.abs(offset.x) + this.blur) / object.width, NUM_FRACTION_DIGITS) * 100 + BLUR_BOX; 5769 fBoxY = toFixed((Math.abs(offset.y) + this.blur) / object.height, NUM_FRACTION_DIGITS) * 100 + BLUR_BOX; 5770 } 5771 if (object.flipX) { 5772 offset.x *= -1; 5773 } 5774 if (object.flipY) { 5775 offset.y *= -1; 5776 } 5777 return ( 5778 '<filter id="SVGID_' + this.id + '" y="-' + fBoxY + '%" height="' + (100 + 2 * fBoxY) + '%" ' + 5779 'x="-' + fBoxX + '%" width="' + (100 + 2 * fBoxX) + '%" ' + '>\n' + 5780 '\t<feGaussianBlur in="SourceAlpha" stdDeviation="' + 5781 toFixed(this.blur ? this.blur / 2 : 0, NUM_FRACTION_DIGITS) + '"></feGaussianBlur>\n' + 5782 '\t<feOffset dx="' + toFixed(offset.x, NUM_FRACTION_DIGITS) + 5783 '" dy="' + toFixed(offset.y, NUM_FRACTION_DIGITS) + '" result="oBlur" ></feOffset>\n' + 5784 '\t<feFlood flood-color="' + this.color + '"/>\n' + 5785 '\t<feComposite in2="oBlur" operator="in" />\n' + 5786 '\t<feMerge>\n' + 5787 '\t\t<feMergeNode></feMergeNode>\n' + 5788 '\t\t<feMergeNode in="SourceGraphic"></feMergeNode>\n' + 5789 '\t</feMerge>\n' + 5790 '</filter>\n'); 5791 }, 5792 /* _TO_SVG_END_ */ 5793 5794 /** 5795 * Returns object representation of a shadow 5796 * @return {Object} Object representation of a shadow instance 5797 */ 5798 toObject: function() { 5799 if (this.includeDefaultValues) { 5800 return { 5801 color: this.color, 5802 blur: this.blur, 5803 offsetX: this.offsetX, 5804 offsetY: this.offsetY, 5805 affectStroke: this.affectStroke 5806 }; 5807 } 5808 var obj = { }, proto = fabric.Shadow.prototype; 5809 5810 ['color', 'blur', 'offsetX', 'offsetY', 'affectStroke'].forEach(function(prop) { 5811 if (this[prop] !== proto[prop]) { 5812 obj[prop] = this[prop]; 5813 } 5814 }, this); 5815 5816 return obj; 5817 } 5818 }); 5819 5820 /** 5821 * Regex matching shadow offsetX, offsetY and blur (ex: "2px 2px 10px rgba(0,0,0,0.2)", "rgb(0,255,0) 2px 2px") 5822 * @static 5823 * @field 5824 * @memberOf fabric.Shadow 5825 */ 5826 fabric.Shadow.reOffsetsAndBlur = /(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/; 5827 5828 })(typeof exports !== 'undefined' ? exports : this); 5829 5830 5831 (function () { 5832 5833 'use strict'; 5834 5835 if (fabric.StaticCanvas) { 5836 fabric.warn('fabric.StaticCanvas is already defined.'); 5837 return; 5838 } 5839 5840 // aliases for faster resolution 5841 var extend = fabric.util.object.extend, 5842 getElementOffset = fabric.util.getElementOffset, 5843 removeFromArray = fabric.util.removeFromArray, 5844 toFixed = fabric.util.toFixed, 5845 5846 CANVAS_INIT_ERROR = new Error('Could not initialize `canvas` element'); 5847 5848 /** 5849 * Static canvas class 5850 * @class fabric.StaticCanvas 5851 * @mixes fabric.Collection 5852 * @mixes fabric.Observable 5853 * @see {@link http://fabricjs.com/static_canvas/|StaticCanvas demo} 5854 * @see {@link fabric.StaticCanvas#initialize} for constructor definition 5855 * @fires before:render 5856 * @fires after:render 5857 * @fires canvas:cleared 5858 * @fires object:added 5859 * @fires object:removed 5860 */ 5861 fabric.StaticCanvas = fabric.util.createClass(/** @lends fabric.StaticCanvas.prototype */ { 5862 5863 /** 5864 * Constructor 5865 * @param {HTMLElement | String} el <canvas> element to initialize instance on 5866 * @param {Object} [options] Options object 5867 * @return {Object} thisArg 5868 */ 5869 initialize: function(el, options) { 5870 options || (options = { }); 5871 5872 this._initStatic(el, options); 5873 }, 5874 5875 /** 5876 * Background color of canvas instance. 5877 * Should be set via {@link fabric.StaticCanvas#setBackgroundColor}. 5878 * @type {(String|fabric.Pattern)} 5879 * @default 5880 */ 5881 backgroundColor: '', 5882 5883 /** 5884 * Background image of canvas instance. 5885 * Should be set via {@link fabric.StaticCanvas#setBackgroundImage}. 5886 * <b>Backwards incompatibility note:</b> The "backgroundImageOpacity" 5887 * and "backgroundImageStretch" properties are deprecated since 1.3.9. 5888 * Use {@link fabric.Image#opacity}, {@link fabric.Image#width} and {@link fabric.Image#height}. 5889 * @type fabric.Image 5890 * @default 5891 */ 5892 backgroundImage: null, 5893 5894 /** 5895 * Overlay color of canvas instance. 5896 * Should be set via {@link fabric.StaticCanvas#setOverlayColor} 5897 * @since 1.3.9 5898 * @type {(String|fabric.Pattern)} 5899 * @default 5900 */ 5901 overlayColor: '', 5902 5903 /** 5904 * Overlay image of canvas instance. 5905 * Should be set via {@link fabric.StaticCanvas#setOverlayImage}. 5906 * <b>Backwards incompatibility note:</b> The "overlayImageLeft" 5907 * and "overlayImageTop" properties are deprecated since 1.3.9. 5908 * Use {@link fabric.Image#left} and {@link fabric.Image#top}. 5909 * @type fabric.Image 5910 * @default 5911 */ 5912 overlayImage: null, 5913 5914 /** 5915 * Indicates whether toObject/toDatalessObject should include default values 5916 * @type Boolean 5917 * @default 5918 */ 5919 includeDefaultValues: true, 5920 5921 /** 5922 * Indicates whether objects' state should be saved 5923 * @type Boolean 5924 * @default 5925 */ 5926 stateful: true, 5927 5928 /** 5929 * Indicates whether {@link fabric.Collection.add}, {@link fabric.Collection.insertAt} and {@link fabric.Collection.remove} should also re-render canvas. 5930 * Disabling this option could give a great performance boost when adding/removing a lot of objects to/from canvas at once 5931 * (followed by a manual rendering after addition/deletion) 5932 * @type Boolean 5933 * @default 5934 */ 5935 renderOnAddRemove: true, 5936 5937 /** 5938 * Function that determines clipping of entire canvas area 5939 * Being passed context as first argument. See clipping canvas area in {@link https://github.com/kangax/fabric.js/wiki/FAQ} 5940 * @type Function 5941 * @default 5942 */ 5943 clipTo: null, 5944 5945 /** 5946 * Indicates whether object controls (borders/controls) are rendered above overlay image 5947 * @type Boolean 5948 * @default 5949 */ 5950 controlsAboveOverlay: false, 5951 5952 /** 5953 * Indicates whether the browser can be scrolled when using a touchscreen and dragging on the canvas 5954 * @type Boolean 5955 * @default 5956 */ 5957 allowTouchScrolling: false, 5958 5959 /** 5960 * Indicates whether this canvas will use image smoothing, this is on by default in browsers 5961 * @type Boolean 5962 * @default 5963 */ 5964 imageSmoothingEnabled: true, 5965 5966 /** 5967 * Indicates whether objects should remain in current stack position when selected. When false objects are brought to top and rendered as part of the selection group 5968 * @type Boolean 5969 * @default 5970 */ 5971 preserveObjectStacking: false, 5972 5973 /** 5974 * The transformation (in the format of Canvas transform) which focuses the viewport 5975 * @type Array 5976 * @default 5977 */ 5978 viewportTransform: [1, 0, 0, 1, 0, 0], 5979 5980 /** 5981 * Callback; invoked right before object is about to be scaled/rotated 5982 */ 5983 onBeforeScaleRotate: function () { 5984 /* NOOP */ 5985 }, 5986 5987 /** 5988 * When true, canvas is scaled by devicePixelRatio for better rendering on retina screens 5989 */ 5990 enableRetinaScaling: true, 5991 5992 /** 5993 * @private 5994 * @param {HTMLElement | String} el <canvas> element to initialize instance on 5995 * @param {Object} [options] Options object 5996 */ 5997 _initStatic: function(el, options) { 5998 this._objects = []; 5999 6000 this._createLowerCanvas(el); 6001 this._initOptions(options); 6002 this._setImageSmoothing(); 6003 6004 // only initialize retina scaling once 6005 if (!this.interactive) { 6006 this._initRetinaScaling(); 6007 } 6008 6009 if (options.overlayImage) { 6010 this.setOverlayImage(options.overlayImage, this.renderAll.bind(this)); 6011 } 6012 if (options.backgroundImage) { 6013 this.setBackgroundImage(options.backgroundImage, this.renderAll.bind(this)); 6014 } 6015 if (options.backgroundColor) { 6016 this.setBackgroundColor(options.backgroundColor, this.renderAll.bind(this)); 6017 } 6018 if (options.overlayColor) { 6019 this.setOverlayColor(options.overlayColor, this.renderAll.bind(this)); 6020 } 6021 this.calcOffset(); 6022 }, 6023 6024 /** 6025 * @private 6026 */ 6027 _isRetinaScaling: function() { 6028 return (fabric.devicePixelRatio !== 1 && this.enableRetinaScaling); 6029 }, 6030 6031 /** 6032 * @private 6033 */ 6034 _initRetinaScaling: function() { 6035 if (!this._isRetinaScaling()) { 6036 return; 6037 } 6038 6039 this.lowerCanvasEl.setAttribute('width', this.width * fabric.devicePixelRatio); 6040 this.lowerCanvasEl.setAttribute('height', this.height * fabric.devicePixelRatio); 6041 6042 this.contextContainer.scale(fabric.devicePixelRatio, fabric.devicePixelRatio); 6043 }, 6044 6045 /** 6046 * Calculates canvas element offset relative to the document 6047 * This method is also attached as "resize" event handler of window 6048 * @return {fabric.Canvas} instance 6049 * @chainable 6050 */ 6051 calcOffset: function () { 6052 this._offset = getElementOffset(this.lowerCanvasEl); 6053 return this; 6054 }, 6055 6056 /** 6057 * Sets {@link fabric.StaticCanvas#overlayImage|overlay image} for this canvas 6058 * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set overlay to 6059 * @param {Function} callback callback to invoke when image is loaded and set as an overlay 6060 * @param {Object} [options] Optional options to set for the {@link fabric.Image|overlay image}. 6061 * @return {fabric.Canvas} thisArg 6062 * @chainable 6063 * @see {@link http://jsfiddle.net/fabricjs/MnzHT/|jsFiddle demo} 6064 * @example <caption>Normal overlayImage with left/top = 0</caption> 6065 * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { 6066 * // Needed to position overlayImage at 0/0 6067 * originX: 'left', 6068 * originY: 'top' 6069 * }); 6070 * @example <caption>overlayImage with different properties</caption> 6071 * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { 6072 * opacity: 0.5, 6073 * angle: 45, 6074 * left: 400, 6075 * top: 400, 6076 * originX: 'left', 6077 * originY: 'top' 6078 * }); 6079 * @example <caption>Stretched overlayImage #1 - width/height correspond to canvas width/height</caption> 6080 * fabric.Image.fromURL('http://fabricjs.com/assets/jail_cell_bars.png', function(img) { 6081 * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'}); 6082 * canvas.setOverlayImage(img, canvas.renderAll.bind(canvas)); 6083 * }); 6084 * @example <caption>Stretched overlayImage #2 - width/height correspond to canvas width/height</caption> 6085 * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { 6086 * width: canvas.width, 6087 * height: canvas.height, 6088 * // Needed to position overlayImage at 0/0 6089 * originX: 'left', 6090 * originY: 'top' 6091 * }); 6092 * @example <caption>overlayImage loaded from cross-origin</caption> 6093 * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { 6094 * opacity: 0.5, 6095 * angle: 45, 6096 * left: 400, 6097 * top: 400, 6098 * originX: 'left', 6099 * originY: 'top', 6100 * crossOrigin: 'anonymous' 6101 * }); 6102 */ 6103 setOverlayImage: function (image, callback, options) { 6104 return this.__setBgOverlayImage('overlayImage', image, callback, options); 6105 }, 6106 6107 /** 6108 * Sets {@link fabric.StaticCanvas#backgroundImage|background image} for this canvas 6109 * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set background to 6110 * @param {Function} callback Callback to invoke when image is loaded and set as background 6111 * @param {Object} [options] Optional options to set for the {@link fabric.Image|background image}. 6112 * @return {fabric.Canvas} thisArg 6113 * @chainable 6114 * @see {@link http://jsfiddle.net/fabricjs/YH9yD/|jsFiddle demo} 6115 * @example <caption>Normal backgroundImage with left/top = 0</caption> 6116 * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { 6117 * // Needed to position backgroundImage at 0/0 6118 * originX: 'left', 6119 * originY: 'top' 6120 * }); 6121 * @example <caption>backgroundImage with different properties</caption> 6122 * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { 6123 * opacity: 0.5, 6124 * angle: 45, 6125 * left: 400, 6126 * top: 400, 6127 * originX: 'left', 6128 * originY: 'top' 6129 * }); 6130 * @example <caption>Stretched backgroundImage #1 - width/height correspond to canvas width/height</caption> 6131 * fabric.Image.fromURL('http://fabricjs.com/assets/honey_im_subtle.png', function(img) { 6132 * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'}); 6133 * canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas)); 6134 * }); 6135 * @example <caption>Stretched backgroundImage #2 - width/height correspond to canvas width/height</caption> 6136 * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { 6137 * width: canvas.width, 6138 * height: canvas.height, 6139 * // Needed to position backgroundImage at 0/0 6140 * originX: 'left', 6141 * originY: 'top' 6142 * }); 6143 * @example <caption>backgroundImage loaded from cross-origin</caption> 6144 * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { 6145 * opacity: 0.5, 6146 * angle: 45, 6147 * left: 400, 6148 * top: 400, 6149 * originX: 'left', 6150 * originY: 'top', 6151 * crossOrigin: 'anonymous' 6152 * }); 6153 */ 6154 setBackgroundImage: function (image, callback, options) { 6155 return this.__setBgOverlayImage('backgroundImage', image, callback, options); 6156 }, 6157 6158 /** 6159 * Sets {@link fabric.StaticCanvas#overlayColor|background color} for this canvas 6160 * @param {(String|fabric.Pattern)} overlayColor Color or pattern to set background color to 6161 * @param {Function} callback Callback to invoke when background color is set 6162 * @return {fabric.Canvas} thisArg 6163 * @chainable 6164 * @see {@link http://jsfiddle.net/fabricjs/pB55h/|jsFiddle demo} 6165 * @example <caption>Normal overlayColor - color value</caption> 6166 * canvas.setOverlayColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas)); 6167 * @example <caption>fabric.Pattern used as overlayColor</caption> 6168 * canvas.setOverlayColor({ 6169 * source: 'http://fabricjs.com/assets/escheresque_ste.png' 6170 * }, canvas.renderAll.bind(canvas)); 6171 * @example <caption>fabric.Pattern used as overlayColor with repeat and offset</caption> 6172 * canvas.setOverlayColor({ 6173 * source: 'http://fabricjs.com/assets/escheresque_ste.png', 6174 * repeat: 'repeat', 6175 * offsetX: 200, 6176 * offsetY: 100 6177 * }, canvas.renderAll.bind(canvas)); 6178 */ 6179 setOverlayColor: function(overlayColor, callback) { 6180 return this.__setBgOverlayColor('overlayColor', overlayColor, callback); 6181 }, 6182 6183 /** 6184 * Sets {@link fabric.StaticCanvas#backgroundColor|background color} for this canvas 6185 * @param {(String|fabric.Pattern)} backgroundColor Color or pattern to set background color to 6186 * @param {Function} callback Callback to invoke when background color is set 6187 * @return {fabric.Canvas} thisArg 6188 * @chainable 6189 * @see {@link http://jsfiddle.net/fabricjs/hXzvk/|jsFiddle demo} 6190 * @example <caption>Normal backgroundColor - color value</caption> 6191 * canvas.setBackgroundColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas)); 6192 * @example <caption>fabric.Pattern used as backgroundColor</caption> 6193 * canvas.setBackgroundColor({ 6194 * source: 'http://fabricjs.com/assets/escheresque_ste.png' 6195 * }, canvas.renderAll.bind(canvas)); 6196 * @example <caption>fabric.Pattern used as backgroundColor with repeat and offset</caption> 6197 * canvas.setBackgroundColor({ 6198 * source: 'http://fabricjs.com/assets/escheresque_ste.png', 6199 * repeat: 'repeat', 6200 * offsetX: 200, 6201 * offsetY: 100 6202 * }, canvas.renderAll.bind(canvas)); 6203 */ 6204 setBackgroundColor: function(backgroundColor, callback) { 6205 return this.__setBgOverlayColor('backgroundColor', backgroundColor, callback); 6206 }, 6207 6208 /** 6209 * @private 6210 * @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-imagesmoothingenabled|WhatWG Canvas Standard} 6211 */ 6212 _setImageSmoothing: function() { 6213 var ctx = this.getContext(); 6214 6215 if (typeof ctx.imageSmoothingEnabled !== 'undefined') { 6216 ctx.imageSmoothingEnabled = this.imageSmoothingEnabled; 6217 return; 6218 } 6219 ctx.webkitImageSmoothingEnabled = this.imageSmoothingEnabled; 6220 ctx.mozImageSmoothingEnabled = this.imageSmoothingEnabled; 6221 ctx.msImageSmoothingEnabled = this.imageSmoothingEnabled; 6222 ctx.oImageSmoothingEnabled = this.imageSmoothingEnabled; 6223 }, 6224 6225 /** 6226 * @private 6227 * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundImage|backgroundImage} 6228 * or {@link fabric.StaticCanvas#overlayImage|overlayImage}) 6229 * @param {(fabric.Image|String|null)} image fabric.Image instance, URL of an image or null to set background or overlay to 6230 * @param {Function} callback Callback to invoke when image is loaded and set as background or overlay 6231 * @param {Object} [options] Optional options to set for the {@link fabric.Image|image}. 6232 */ 6233 __setBgOverlayImage: function(property, image, callback, options) { 6234 if (typeof image === 'string') { 6235 fabric.util.loadImage(image, function(img) { 6236 this[property] = new fabric.Image(img, options); 6237 callback && callback(img); 6238 }, this, options && options.crossOrigin); 6239 } 6240 else { 6241 options && image.setOptions(options); 6242 this[property] = image; 6243 callback && callback(image); 6244 } 6245 6246 return this; 6247 }, 6248 6249 /** 6250 * @private 6251 * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundColor|backgroundColor} 6252 * or {@link fabric.StaticCanvas#overlayColor|overlayColor}) 6253 * @param {(Object|String|null)} color Object with pattern information, color value or null 6254 * @param {Function} [callback] Callback is invoked when color is set 6255 */ 6256 __setBgOverlayColor: function(property, color, callback) { 6257 if (color && color.source) { 6258 var _this = this; 6259 fabric.util.loadImage(color.source, function(img) { 6260 _this[property] = new fabric.Pattern({ 6261 source: img, 6262 repeat: color.repeat, 6263 offsetX: color.offsetX, 6264 offsetY: color.offsetY 6265 }); 6266 callback && callback(); 6267 }); 6268 } 6269 else { 6270 this[property] = color; 6271 callback && callback(); 6272 } 6273 6274 return this; 6275 }, 6276 6277 /** 6278 * @private 6279 */ 6280 _createCanvasElement: function() { 6281 var element = fabric.document.createElement('canvas'); 6282 if (!element.style) { 6283 element.style = { }; 6284 } 6285 if (!element) { 6286 throw CANVAS_INIT_ERROR; 6287 } 6288 this._initCanvasElement(element); 6289 return element; 6290 }, 6291 6292 /** 6293 * @private 6294 * @param {HTMLElement} element 6295 */ 6296 _initCanvasElement: function(element) { 6297 fabric.util.createCanvasElement(element); 6298 6299 if (typeof element.getContext === 'undefined') { 6300 throw CANVAS_INIT_ERROR; 6301 } 6302 }, 6303 6304 /** 6305 * @private 6306 * @param {Object} [options] Options object 6307 */ 6308 _initOptions: function (options) { 6309 for (var prop in options) { 6310 this[prop] = options[prop]; 6311 } 6312 6313 this.width = this.width || parseInt(this.lowerCanvasEl.width, 10) || 0; 6314 this.height = this.height || parseInt(this.lowerCanvasEl.height, 10) || 0; 6315 6316 if (!this.lowerCanvasEl.style) { 6317 return; 6318 } 6319 6320 this.lowerCanvasEl.width = this.width; 6321 this.lowerCanvasEl.height = this.height; 6322 6323 this.lowerCanvasEl.style.width = this.width + 'px'; 6324 this.lowerCanvasEl.style.height = this.height + 'px'; 6325 6326 this.viewportTransform = this.viewportTransform.slice(); 6327 }, 6328 6329 /** 6330 * Creates a bottom canvas 6331 * @private 6332 * @param {HTMLElement} [canvasEl] 6333 */ 6334 _createLowerCanvas: function (canvasEl) { 6335 this.lowerCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement(); 6336 this._initCanvasElement(this.lowerCanvasEl); 6337 6338 fabric.util.addClass(this.lowerCanvasEl, 'lower-canvas'); 6339 6340 if (this.interactive) { 6341 this._applyCanvasStyle(this.lowerCanvasEl); 6342 } 6343 6344 this.contextContainer = this.lowerCanvasEl.getContext('2d'); 6345 }, 6346 6347 /** 6348 * Returns canvas width (in px) 6349 * @return {Number} 6350 */ 6351 getWidth: function () { 6352 return this.width; 6353 }, 6354 6355 /** 6356 * Returns canvas height (in px) 6357 * @return {Number} 6358 */ 6359 getHeight: function () { 6360 return this.height; 6361 }, 6362 6363 /** 6364 * Sets width of this canvas instance 6365 * @param {Number|String} value Value to set width to 6366 * @param {Object} [options] Options object 6367 * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions 6368 * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions 6369 * @return {fabric.Canvas} instance 6370 * @chainable true 6371 */ 6372 setWidth: function (value, options) { 6373 return this.setDimensions({ width: value }, options); 6374 }, 6375 6376 /** 6377 * Sets height of this canvas instance 6378 * @param {Number|String} value Value to set height to 6379 * @param {Object} [options] Options object 6380 * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions 6381 * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions 6382 * @return {fabric.Canvas} instance 6383 * @chainable true 6384 */ 6385 setHeight: function (value, options) { 6386 return this.setDimensions({ height: value }, options); 6387 }, 6388 6389 /** 6390 * Sets dimensions (width, height) of this canvas instance. when options.cssOnly flag active you should also supply the unit of measure (px/%/em) 6391 * @param {Object} dimensions Object with width/height properties 6392 * @param {Number|String} [dimensions.width] Width of canvas element 6393 * @param {Number|String} [dimensions.height] Height of canvas element 6394 * @param {Object} [options] Options object 6395 * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions 6396 * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions 6397 * @return {fabric.Canvas} thisArg 6398 * @chainable 6399 */ 6400 setDimensions: function (dimensions, options) { 6401 var cssValue; 6402 6403 options = options || {}; 6404 6405 for (var prop in dimensions) { 6406 cssValue = dimensions[prop]; 6407 6408 if (!options.cssOnly) { 6409 this._setBackstoreDimension(prop, dimensions[prop]); 6410 cssValue += 'px'; 6411 } 6412 6413 if (!options.backstoreOnly) { 6414 this._setCssDimension(prop, cssValue); 6415 } 6416 } 6417 this._initRetinaScaling(); 6418 this._setImageSmoothing(); 6419 this.calcOffset(); 6420 6421 if (!options.cssOnly) { 6422 this.renderAll(); 6423 } 6424 6425 return this; 6426 }, 6427 6428 /** 6429 * Helper for setting width/height 6430 * @private 6431 * @param {String} prop property (width|height) 6432 * @param {Number} value value to set property to 6433 * @return {fabric.Canvas} instance 6434 * @chainable true 6435 */ 6436 _setBackstoreDimension: function (prop, value) { 6437 this.lowerCanvasEl[prop] = value; 6438 6439 if (this.upperCanvasEl) { 6440 this.upperCanvasEl[prop] = value; 6441 } 6442 6443 if (this.cacheCanvasEl) { 6444 this.cacheCanvasEl[prop] = value; 6445 } 6446 6447 this[prop] = value; 6448 6449 return this; 6450 }, 6451 6452 /** 6453 * Helper for setting css width/height 6454 * @private 6455 * @param {String} prop property (width|height) 6456 * @param {String} value value to set property to 6457 * @return {fabric.Canvas} instance 6458 * @chainable true 6459 */ 6460 _setCssDimension: function (prop, value) { 6461 this.lowerCanvasEl.style[prop] = value; 6462 6463 if (this.upperCanvasEl) { 6464 this.upperCanvasEl.style[prop] = value; 6465 } 6466 6467 if (this.wrapperEl) { 6468 this.wrapperEl.style[prop] = value; 6469 } 6470 6471 return this; 6472 }, 6473 6474 /** 6475 * Returns canvas zoom level 6476 * @return {Number} 6477 */ 6478 getZoom: function () { 6479 return Math.sqrt(this.viewportTransform[0] * this.viewportTransform[3]); 6480 }, 6481 6482 /** 6483 * Sets viewport transform of this canvas instance 6484 * @param {Array} vpt the transform in the form of context.transform 6485 * @return {fabric.Canvas} instance 6486 * @chainable true 6487 */ 6488 setViewportTransform: function (vpt) { 6489 var activeGroup = this.getActiveGroup(); 6490 this.viewportTransform = vpt; 6491 this.renderAll(); 6492 for (var i = 0, len = this._objects.length; i < len; i++) { 6493 this._objects[i].setCoords(); 6494 } 6495 if (activeGroup) { 6496 activeGroup.setCoords(); 6497 } 6498 return this; 6499 }, 6500 6501 /** 6502 * Sets zoom level of this canvas instance, zoom centered around point 6503 * @param {fabric.Point} point to zoom with respect to 6504 * @param {Number} value to set zoom to, less than 1 zooms out 6505 * @return {fabric.Canvas} instance 6506 * @chainable true 6507 */ 6508 zoomToPoint: function (point, value) { 6509 // TODO: just change the scale, preserve other transformations 6510 var before = point; 6511 point = fabric.util.transformPoint(point, fabric.util.invertTransform(this.viewportTransform)); 6512 this.viewportTransform[0] = value; 6513 this.viewportTransform[3] = value; 6514 var after = fabric.util.transformPoint(point, this.viewportTransform); 6515 this.viewportTransform[4] += before.x - after.x; 6516 this.viewportTransform[5] += before.y - after.y; 6517 this.renderAll(); 6518 for (var i = 0, len = this._objects.length; i < len; i++) { 6519 this._objects[i].setCoords(); 6520 } 6521 return this; 6522 }, 6523 6524 /** 6525 * Sets zoom level of this canvas instance 6526 * @param {Number} value to set zoom to, less than 1 zooms out 6527 * @return {fabric.Canvas} instance 6528 * @chainable true 6529 */ 6530 setZoom: function (value) { 6531 this.zoomToPoint(new fabric.Point(0, 0), value); 6532 return this; 6533 }, 6534 6535 /** 6536 * Pan viewport so as to place point at top left corner of canvas 6537 * @param {fabric.Point} point to move to 6538 * @return {fabric.Canvas} instance 6539 * @chainable true 6540 */ 6541 absolutePan: function (point) { 6542 this.viewportTransform[4] = -point.x; 6543 this.viewportTransform[5] = -point.y; 6544 this.renderAll(); 6545 for (var i = 0, len = this._objects.length; i < len; i++) { 6546 this._objects[i].setCoords(); 6547 } 6548 return this; 6549 }, 6550 6551 /** 6552 * Pans viewpoint relatively 6553 * @param {fabric.Point} point (position vector) to move by 6554 * @return {fabric.Canvas} instance 6555 * @chainable true 6556 */ 6557 relativePan: function (point) { 6558 return this.absolutePan(new fabric.Point( 6559 -point.x - this.viewportTransform[4], 6560 -point.y - this.viewportTransform[5] 6561 )); 6562 }, 6563 6564 /** 6565 * Returns <canvas> element corresponding to this instance 6566 * @return {HTMLCanvasElement} 6567 */ 6568 getElement: function () { 6569 return this.lowerCanvasEl; 6570 }, 6571 6572 /** 6573 * Returns currently selected object, if any 6574 * @return {fabric.Object} 6575 */ 6576 getActiveObject: function() { 6577 return null; 6578 }, 6579 6580 /** 6581 * Returns currently selected group of object, if any 6582 * @return {fabric.Group} 6583 */ 6584 getActiveGroup: function() { 6585 return null; 6586 }, 6587 6588 /** 6589 * @private 6590 * @param {fabric.Object} obj Object that was added 6591 */ 6592 _onObjectAdded: function(obj) { 6593 this.stateful && obj.setupState(); 6594 obj._set('canvas', this); 6595 obj.setCoords(); 6596 this.fire('object:added', { target: obj }); 6597 obj.fire('added'); 6598 }, 6599 6600 /** 6601 * @private 6602 * @param {fabric.Object} obj Object that was removed 6603 */ 6604 _onObjectRemoved: function(obj) { 6605 // removing active object should fire "selection:cleared" events 6606 if (this.getActiveObject() === obj) { 6607 this.fire('before:selection:cleared', { target: obj }); 6608 this._discardActiveObject(); 6609 this.fire('selection:cleared'); 6610 } 6611 6612 this.fire('object:removed', { target: obj }); 6613 obj.fire('removed'); 6614 }, 6615 6616 /** 6617 * Clears specified context of canvas element 6618 * @param {CanvasRenderingContext2D} ctx Context to clear 6619 * @return {fabric.Canvas} thisArg 6620 * @chainable 6621 */ 6622 clearContext: function(ctx) { 6623 ctx.clearRect(0, 0, this.width, this.height); 6624 return this; 6625 }, 6626 6627 /** 6628 * Returns context of canvas where objects are drawn 6629 * @return {CanvasRenderingContext2D} 6630 */ 6631 getContext: function () { 6632 return this.contextContainer; 6633 }, 6634 6635 /** 6636 * Clears all contexts (background, main, top) of an instance 6637 * @return {fabric.Canvas} thisArg 6638 * @chainable 6639 */ 6640 clear: function () { 6641 this._objects.length = 0; 6642 if (this.discardActiveGroup) { 6643 this.discardActiveGroup(); 6644 } 6645 if (this.discardActiveObject) { 6646 this.discardActiveObject(); 6647 } 6648 this.clearContext(this.contextContainer); 6649 if (this.contextTop) { 6650 this.clearContext(this.contextTop); 6651 } 6652 this.fire('canvas:cleared'); 6653 this.renderAll(); 6654 return this; 6655 }, 6656 6657 /** 6658 * Divides objects in two groups, one to render immediately 6659 * and one to render as activeGroup. 6660 * return objects to render immediately and pushes the other in the activeGroup. 6661 */ 6662 _chooseObjectsToRender: function() { 6663 var activeGroup = this.getActiveGroup(), 6664 object, objsToRender = [ ], activeGroupObjects = [ ]; 6665 6666 if (activeGroup && !this.preserveObjectStacking) { 6667 for (var i = 0, length = this._objects.length; i < length; i++) { 6668 object = this._objects[i]; 6669 if (!activeGroup.contains(object)) { 6670 objsToRender.push(object); 6671 } 6672 else { 6673 activeGroupObjects.push(object); 6674 } 6675 } 6676 activeGroup._set('_objects', activeGroupObjects); 6677 } 6678 else { 6679 objsToRender = this._objects; 6680 } 6681 return objsToRender; 6682 }, 6683 6684 /** 6685 * Renders both the top canvas and the secondary container canvas. 6686 * @param {Boolean} [allOnTop] Whether we want to force all images to be rendered on the top canvas 6687 * @return {fabric.Canvas} instance 6688 * @chainable 6689 */ 6690 renderAll: function () { 6691 var canvasToDrawOn = this.contextContainer, objsToRender; 6692 6693 if (this.contextTop && this.selection && !this._groupSelector) { 6694 this.clearContext(this.contextTop); 6695 } 6696 6697 this.clearContext(canvasToDrawOn); 6698 6699 this.fire('before:render'); 6700 6701 if (this.clipTo) { 6702 fabric.util.clipContext(this, canvasToDrawOn); 6703 } 6704 this._renderBackground(canvasToDrawOn); 6705 6706 canvasToDrawOn.save(); 6707 objsToRender = this._chooseObjectsToRender(); 6708 //apply viewport transform once for all rendering process 6709 canvasToDrawOn.transform.apply(canvasToDrawOn, this.viewportTransform); 6710 this._renderObjects(canvasToDrawOn, objsToRender); 6711 this.preserveObjectStacking || this._renderObjects(canvasToDrawOn, [this.getActiveGroup()]); 6712 canvasToDrawOn.restore(); 6713 6714 if (!this.controlsAboveOverlay && this.interactive) { 6715 this.drawControls(canvasToDrawOn); 6716 } 6717 if (this.clipTo) { 6718 canvasToDrawOn.restore(); 6719 } 6720 this._renderOverlay(canvasToDrawOn); 6721 if (this.controlsAboveOverlay && this.interactive) { 6722 this.drawControls(canvasToDrawOn); 6723 } 6724 6725 this.fire('after:render'); 6726 return this; 6727 }, 6728 6729 /** 6730 * @private 6731 * @param {CanvasRenderingContext2D} ctx Context to render on 6732 * @param {Array} objects to render 6733 */ 6734 _renderObjects: function(ctx, objects) { 6735 for (var i = 0, length = objects.length; i < length; ++i) { 6736 objects[i] && objects[i].render(ctx); 6737 } 6738 }, 6739 6740 /** 6741 * @private 6742 * @param {CanvasRenderingContext2D} ctx Context to render on 6743 * @param {string} property 'background' or 'overlay' 6744 */ 6745 _renderBackgroundOrOverlay: function(ctx, property) { 6746 var object = this[property + 'Color']; 6747 if (object) { 6748 ctx.fillStyle = object.toLive 6749 ? object.toLive(ctx) 6750 : object; 6751 6752 ctx.fillRect( 6753 object.offsetX || 0, 6754 object.offsetY || 0, 6755 this.width, 6756 this.height); 6757 } 6758 object = this[property + 'Image']; 6759 if (object) { 6760 object.render(ctx); 6761 } 6762 }, 6763 6764 /** 6765 * @private 6766 * @param {CanvasRenderingContext2D} ctx Context to render on 6767 */ 6768 _renderBackground: function(ctx) { 6769 this._renderBackgroundOrOverlay(ctx, 'background'); 6770 }, 6771 6772 /** 6773 * @private 6774 * @param {CanvasRenderingContext2D} ctx Context to render on 6775 */ 6776 _renderOverlay: function(ctx) { 6777 this._renderBackgroundOrOverlay(ctx, 'overlay'); 6778 }, 6779 6780 /** 6781 * Method to render only the top canvas. 6782 * Also used to render the group selection box. 6783 * @return {fabric.Canvas} thisArg 6784 * @chainable 6785 */ 6786 renderTop: function () { 6787 var ctx = this.contextTop || this.contextContainer; 6788 this.clearContext(ctx); 6789 6790 // we render the top context - last object 6791 if (this.selection && this._groupSelector) { 6792 this._drawSelection(); 6793 } 6794 6795 this.fire('after:render'); 6796 6797 return this; 6798 }, 6799 6800 /** 6801 * Returns coordinates of a center of canvas. 6802 * Returned value is an object with top and left properties 6803 * @return {Object} object with "top" and "left" number values 6804 */ 6805 getCenter: function () { 6806 return { 6807 top: this.getHeight() / 2, 6808 left: this.getWidth() / 2 6809 }; 6810 }, 6811 6812 /** 6813 * Centers object horizontally. 6814 * You might need to call `setCoords` on an object after centering, to update controls area. 6815 * @param {fabric.Object} object Object to center horizontally 6816 * @return {fabric.Canvas} thisArg 6817 */ 6818 centerObjectH: function (object) { 6819 this._centerObject(object, new fabric.Point(this.getCenter().left, object.getCenterPoint().y)); 6820 this.renderAll(); 6821 return this; 6822 }, 6823 6824 /** 6825 * Centers object vertically. 6826 * You might need to call `setCoords` on an object after centering, to update controls area. 6827 * @param {fabric.Object} object Object to center vertically 6828 * @return {fabric.Canvas} thisArg 6829 * @chainable 6830 */ 6831 centerObjectV: function (object) { 6832 this._centerObject(object, new fabric.Point(object.getCenterPoint().x, this.getCenter().top)); 6833 this.renderAll(); 6834 return this; 6835 }, 6836 6837 /** 6838 * Centers object vertically and horizontally. 6839 * You might need to call `setCoords` on an object after centering, to update controls area. 6840 * @param {fabric.Object} object Object to center vertically and horizontally 6841 * @return {fabric.Canvas} thisArg 6842 * @chainable 6843 */ 6844 centerObject: function(object) { 6845 var center = this.getCenter(); 6846 6847 this._centerObject(object, new fabric.Point(center.left, center.top)); 6848 this.renderAll(); 6849 return this; 6850 }, 6851 6852 /** 6853 * @private 6854 * @param {fabric.Object} object Object to center 6855 * @param {fabric.Point} center Center point 6856 * @return {fabric.Canvas} thisArg 6857 * @chainable 6858 */ 6859 _centerObject: function(object, center) { 6860 object.setPositionByOrigin(center, 'center', 'center'); 6861 return this; 6862 }, 6863 6864 /** 6865 * Returs dataless JSON representation of canvas 6866 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 6867 * @return {String} json string 6868 */ 6869 toDatalessJSON: function (propertiesToInclude) { 6870 return this.toDatalessObject(propertiesToInclude); 6871 }, 6872 6873 /** 6874 * Returns object representation of canvas 6875 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 6876 * @return {Object} object representation of an instance 6877 */ 6878 toObject: function (propertiesToInclude) { 6879 return this._toObjectMethod('toObject', propertiesToInclude); 6880 }, 6881 6882 /** 6883 * Returns dataless object representation of canvas 6884 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 6885 * @return {Object} object representation of an instance 6886 */ 6887 toDatalessObject: function (propertiesToInclude) { 6888 return this._toObjectMethod('toDatalessObject', propertiesToInclude); 6889 }, 6890 6891 /** 6892 * @private 6893 */ 6894 _toObjectMethod: function (methodName, propertiesToInclude) { 6895 6896 var data = { 6897 objects: this._toObjects(methodName, propertiesToInclude) 6898 }; 6899 6900 extend(data, this.__serializeBgOverlay()); 6901 6902 fabric.util.populateWithProperties(this, data, propertiesToInclude); 6903 6904 return data; 6905 }, 6906 6907 /** 6908 * @private 6909 */ 6910 _toObjects: function(methodName, propertiesToInclude) { 6911 return this.getObjects().map(function(instance) { 6912 return this._toObject(instance, methodName, propertiesToInclude); 6913 }, this); 6914 }, 6915 6916 /** 6917 * @private 6918 */ 6919 _toObject: function(instance, methodName, propertiesToInclude) { 6920 var originalValue; 6921 6922 if (!this.includeDefaultValues) { 6923 originalValue = instance.includeDefaultValues; 6924 instance.includeDefaultValues = false; 6925 } 6926 6927 //If the object is part of the current selection group, it should 6928 //be transformed appropriately 6929 //i.e. it should be serialised as it would appear if the selection group 6930 //were to be destroyed. 6931 var originalProperties = this._realizeGroupTransformOnObject(instance), 6932 object = instance[methodName](propertiesToInclude); 6933 if (!this.includeDefaultValues) { 6934 instance.includeDefaultValues = originalValue; 6935 } 6936 6937 //Undo the damage we did by changing all of its properties 6938 this._unwindGroupTransformOnObject(instance, originalProperties); 6939 6940 return object; 6941 }, 6942 6943 /** 6944 * Realises an object's group transformation on it 6945 * @private 6946 * @param {fabric.Object} [instance] the object to transform (gets mutated) 6947 * @returns the original values of instance which were changed 6948 */ 6949 _realizeGroupTransformOnObject: function(instance) { 6950 var layoutProps = ['angle', 'flipX', 'flipY', 'height', 'left', 'scaleX', 'scaleY', 'top', 'width']; 6951 if (instance.group && instance.group === this.getActiveGroup()) { 6952 //Copy all the positionally relevant properties across now 6953 var originalValues = {}; 6954 layoutProps.forEach(function(prop) { 6955 originalValues[prop] = instance[prop]; 6956 }); 6957 this.getActiveGroup().realizeTransform(instance); 6958 return originalValues; 6959 } 6960 else { 6961 return null; 6962 } 6963 }, 6964 6965 /** 6966 * Restores the changed properties of instance 6967 * @private 6968 * @param {fabric.Object} [instance] the object to un-transform (gets mutated) 6969 * @param {Object} [originalValues] the original values of instance, as returned by _realizeGroupTransformOnObject 6970 */ 6971 _unwindGroupTransformOnObject: function(instance, originalValues) { 6972 if (originalValues) { 6973 instance.set(originalValues); 6974 } 6975 }, 6976 6977 /** 6978 * @private 6979 */ 6980 __serializeBgOverlay: function() { 6981 var data = { 6982 background: (this.backgroundColor && this.backgroundColor.toObject) 6983 ? this.backgroundColor.toObject() 6984 : this.backgroundColor 6985 }; 6986 6987 if (this.overlayColor) { 6988 data.overlay = this.overlayColor.toObject 6989 ? this.overlayColor.toObject() 6990 : this.overlayColor; 6991 } 6992 if (this.backgroundImage) { 6993 data.backgroundImage = this.backgroundImage.toObject(); 6994 } 6995 if (this.overlayImage) { 6996 data.overlayImage = this.overlayImage.toObject(); 6997 } 6998 6999 return data; 7000 }, 7001 7002 /* _TO_SVG_START_ */ 7003 /** 7004 * When true, getSvgTransform() will apply the StaticCanvas.viewportTransform to the SVG transformation. When true, 7005 * a zoomed canvas will then produce zoomed SVG output. 7006 * @type Boolean 7007 * @default 7008 */ 7009 svgViewportTransformation: true, 7010 7011 /** 7012 * Returns SVG representation of canvas 7013 * @function 7014 * @param {Object} [options] Options object for SVG output 7015 * @param {Boolean} [options.suppressPreamble=false] If true xml tag is not included 7016 * @param {Object} [options.viewBox] SVG viewbox object 7017 * @param {Number} [options.viewBox.x] x-cooridnate of viewbox 7018 * @param {Number} [options.viewBox.y] y-coordinate of viewbox 7019 * @param {Number} [options.viewBox.width] Width of viewbox 7020 * @param {Number} [options.viewBox.height] Height of viewbox 7021 * @param {String} [options.encoding=UTF-8] Encoding of SVG output 7022 * @param {String} [options.width] desired width of svg with or without units 7023 * @param {String} [options.height] desired height of svg with or without units 7024 * @param {Function} [reviver] Method for further parsing of svg elements, called after each fabric object converted into svg representation. 7025 * @return {String} SVG string 7026 * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#serialization} 7027 * @see {@link http://jsfiddle.net/fabricjs/jQ3ZZ/|jsFiddle demo} 7028 * @example <caption>Normal SVG output</caption> 7029 * var svg = canvas.toSVG(); 7030 * @example <caption>SVG output without preamble (without <?xml ../>)</caption> 7031 * var svg = canvas.toSVG({suppressPreamble: true}); 7032 * @example <caption>SVG output with viewBox attribute</caption> 7033 * var svg = canvas.toSVG({ 7034 * viewBox: { 7035 * x: 100, 7036 * y: 100, 7037 * width: 200, 7038 * height: 300 7039 * } 7040 * }); 7041 * @example <caption>SVG output with different encoding (default: UTF-8)</caption> 7042 * var svg = canvas.toSVG({encoding: 'ISO-8859-1'}); 7043 * @example <caption>Modify SVG output with reviver function</caption> 7044 * var svg = canvas.toSVG(null, function(svg) { 7045 * return svg.replace('stroke-dasharray: ; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; ', ''); 7046 * }); 7047 */ 7048 toSVG: function(options, reviver) { 7049 options || (options = { }); 7050 7051 var markup = []; 7052 7053 this._setSVGPreamble(markup, options); 7054 this._setSVGHeader(markup, options); 7055 7056 this._setSVGBgOverlayColor(markup, 'backgroundColor'); 7057 this._setSVGBgOverlayImage(markup, 'backgroundImage'); 7058 7059 this._setSVGObjects(markup, reviver); 7060 7061 this._setSVGBgOverlayColor(markup, 'overlayColor'); 7062 this._setSVGBgOverlayImage(markup, 'overlayImage'); 7063 7064 markup.push('</svg>'); 7065 7066 return markup.join(''); 7067 }, 7068 7069 /** 7070 * @private 7071 */ 7072 _setSVGPreamble: function(markup, options) { 7073 if (options.suppressPreamble) { 7074 return; 7075 } 7076 markup.push( 7077 '<?xml version="1.0" encoding="', (options.encoding || 'UTF-8'), '" standalone="no" ?>\n', 7078 '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" ', 7079 '"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' 7080 ); 7081 }, 7082 7083 /** 7084 * @private 7085 */ 7086 _setSVGHeader: function(markup, options) { 7087 var width = options.width || this.width, 7088 height = options.height || this.height, 7089 vpt, viewBox = 'viewBox="0 0 ' + this.width + ' ' + this.height + '" ', 7090 NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; 7091 7092 if (options.viewBox) { 7093 viewBox = 'viewBox="' + 7094 options.viewBox.x + ' ' + 7095 options.viewBox.y + ' ' + 7096 options.viewBox.width + ' ' + 7097 options.viewBox.height + '" '; 7098 } 7099 else { 7100 if (this.svgViewportTransformation) { 7101 vpt = this.viewportTransform; 7102 viewBox = 'viewBox="' + 7103 toFixed(-vpt[4] / vpt[0], NUM_FRACTION_DIGITS) + ' ' + 7104 toFixed(-vpt[5] / vpt[3], NUM_FRACTION_DIGITS) + ' ' + 7105 toFixed(this.width / vpt[0], NUM_FRACTION_DIGITS) + ' ' + 7106 toFixed(this.height / vpt[3], NUM_FRACTION_DIGITS) + '" '; 7107 } 7108 } 7109 7110 markup.push( 7111 '<svg ', 7112 'xmlns="http://www.w3.org/2000/svg" ', 7113 'xmlns:xlink="http://www.w3.org/1999/xlink" ', 7114 'version="1.1" ', 7115 'width="', width, '" ', 7116 'height="', height, '" ', 7117 (this.backgroundColor && !this.backgroundColor.toLive 7118 ? 'style="background-color: ' + this.backgroundColor + '" ' 7119 : null), 7120 viewBox, 7121 'xml:space="preserve">\n', 7122 '<desc>Created with Fabric.js ', fabric.version, '</desc>\n', 7123 '<defs>', 7124 fabric.createSVGFontFacesMarkup(this.getObjects()), 7125 fabric.createSVGRefElementsMarkup(this), 7126 '</defs>\n' 7127 ); 7128 }, 7129 7130 /** 7131 * @private 7132 */ 7133 _setSVGObjects: function(markup, reviver) { 7134 for (var i = 0, objects = this.getObjects(), len = objects.length; i < len; i++) { 7135 var instance = objects[i], 7136 //If the object is in a selection group, simulate what would happen to that 7137 //object when the group is deselected 7138 originalProperties = this._realizeGroupTransformOnObject(instance); 7139 markup.push(instance.toSVG(reviver)); 7140 this._unwindGroupTransformOnObject(instance, originalProperties); 7141 } 7142 }, 7143 7144 /** 7145 * @private 7146 */ 7147 _setSVGBgOverlayImage: function(markup, property) { 7148 if (this[property] && this[property].toSVG) { 7149 markup.push(this[property].toSVG()); 7150 } 7151 }, 7152 7153 /** 7154 * @private 7155 */ 7156 _setSVGBgOverlayColor: function(markup, property) { 7157 if (this[property] && this[property].source) { 7158 markup.push( 7159 '<rect x="', this[property].offsetX, '" y="', this[property].offsetY, '" ', 7160 'width="', 7161 (this[property].repeat === 'repeat-y' || this[property].repeat === 'no-repeat' 7162 ? this[property].source.width 7163 : this.width), 7164 '" height="', 7165 (this[property].repeat === 'repeat-x' || this[property].repeat === 'no-repeat' 7166 ? this[property].source.height 7167 : this.height), 7168 '" fill="url(#' + property + 'Pattern)"', 7169 '></rect>\n' 7170 ); 7171 } 7172 else if (this[property] && property === 'overlayColor') { 7173 markup.push( 7174 '<rect x="0" y="0" ', 7175 'width="', this.width, 7176 '" height="', this.height, 7177 '" fill="', this[property], '"', 7178 '></rect>\n' 7179 ); 7180 } 7181 }, 7182 /* _TO_SVG_END_ */ 7183 7184 /** 7185 * Moves an object to the bottom of the stack of drawn objects 7186 * @param {fabric.Object} object Object to send to back 7187 * @return {fabric.Canvas} thisArg 7188 * @chainable 7189 */ 7190 sendToBack: function (object) { 7191 removeFromArray(this._objects, object); 7192 this._objects.unshift(object); 7193 return this.renderAll && this.renderAll(); 7194 }, 7195 7196 /** 7197 * Moves an object to the top of the stack of drawn objects 7198 * @param {fabric.Object} object Object to send 7199 * @return {fabric.Canvas} thisArg 7200 * @chainable 7201 */ 7202 bringToFront: function (object) { 7203 removeFromArray(this._objects, object); 7204 this._objects.push(object); 7205 return this.renderAll && this.renderAll(); 7206 }, 7207 7208 /** 7209 * Moves an object down in stack of drawn objects 7210 * @param {fabric.Object} object Object to send 7211 * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object 7212 * @return {fabric.Canvas} thisArg 7213 * @chainable 7214 */ 7215 sendBackwards: function (object, intersecting) { 7216 var idx = this._objects.indexOf(object); 7217 7218 // if object is not on the bottom of stack 7219 if (idx !== 0) { 7220 var newIdx = this._findNewLowerIndex(object, idx, intersecting); 7221 7222 removeFromArray(this._objects, object); 7223 this._objects.splice(newIdx, 0, object); 7224 this.renderAll && this.renderAll(); 7225 } 7226 return this; 7227 }, 7228 7229 /** 7230 * @private 7231 */ 7232 _findNewLowerIndex: function(object, idx, intersecting) { 7233 var newIdx; 7234 7235 if (intersecting) { 7236 newIdx = idx; 7237 7238 // traverse down the stack looking for the nearest intersecting object 7239 for (var i = idx - 1; i >= 0; --i) { 7240 7241 var isIntersecting = object.intersectsWithObject(this._objects[i]) || 7242 object.isContainedWithinObject(this._objects[i]) || 7243 this._objects[i].isContainedWithinObject(object); 7244 7245 if (isIntersecting) { 7246 newIdx = i; 7247 break; 7248 } 7249 } 7250 } 7251 else { 7252 newIdx = idx - 1; 7253 } 7254 7255 return newIdx; 7256 }, 7257 7258 /** 7259 * Moves an object up in stack of drawn objects 7260 * @param {fabric.Object} object Object to send 7261 * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object 7262 * @return {fabric.Canvas} thisArg 7263 * @chainable 7264 */ 7265 bringForward: function (object, intersecting) { 7266 var idx = this._objects.indexOf(object); 7267 7268 // if object is not on top of stack (last item in an array) 7269 if (idx !== this._objects.length - 1) { 7270 var newIdx = this._findNewUpperIndex(object, idx, intersecting); 7271 7272 removeFromArray(this._objects, object); 7273 this._objects.splice(newIdx, 0, object); 7274 this.renderAll && this.renderAll(); 7275 } 7276 return this; 7277 }, 7278 7279 /** 7280 * @private 7281 */ 7282 _findNewUpperIndex: function(object, idx, intersecting) { 7283 var newIdx; 7284 7285 if (intersecting) { 7286 newIdx = idx; 7287 7288 // traverse up the stack looking for the nearest intersecting object 7289 for (var i = idx + 1; i < this._objects.length; ++i) { 7290 7291 var isIntersecting = object.intersectsWithObject(this._objects[i]) || 7292 object.isContainedWithinObject(this._objects[i]) || 7293 this._objects[i].isContainedWithinObject(object); 7294 7295 if (isIntersecting) { 7296 newIdx = i; 7297 break; 7298 } 7299 } 7300 } 7301 else { 7302 newIdx = idx + 1; 7303 } 7304 7305 return newIdx; 7306 }, 7307 7308 /** 7309 * Moves an object to specified level in stack of drawn objects 7310 * @param {fabric.Object} object Object to send 7311 * @param {Number} index Position to move to 7312 * @return {fabric.Canvas} thisArg 7313 * @chainable 7314 */ 7315 moveTo: function (object, index) { 7316 removeFromArray(this._objects, object); 7317 this._objects.splice(index, 0, object); 7318 return this.renderAll && this.renderAll(); 7319 }, 7320 7321 /** 7322 * Clears a canvas element and removes all event listeners 7323 * @return {fabric.Canvas} thisArg 7324 * @chainable 7325 */ 7326 dispose: function () { 7327 this.clear(); 7328 return this; 7329 }, 7330 7331 /** 7332 * Returns a string representation of an instance 7333 * @return {String} string representation of an instance 7334 */ 7335 toString: function () { 7336 return '#<fabric.Canvas (' + this.complexity() + '): ' + 7337 '{ objects: ' + this.getObjects().length + ' }>'; 7338 } 7339 }); 7340 7341 extend(fabric.StaticCanvas.prototype, fabric.Observable); 7342 extend(fabric.StaticCanvas.prototype, fabric.Collection); 7343 extend(fabric.StaticCanvas.prototype, fabric.DataURLExporter); 7344 7345 extend(fabric.StaticCanvas, /** @lends fabric.StaticCanvas */ { 7346 7347 /** 7348 * @static 7349 * @type String 7350 * @default 7351 */ 7352 EMPTY_JSON: '{"objects": [], "background": "white"}', 7353 7354 /** 7355 * Provides a way to check support of some of the canvas methods 7356 * (either those of HTMLCanvasElement itself, or rendering context) 7357 * 7358 * @param {String} methodName Method to check support for; 7359 * Could be one of "getImageData", "toDataURL", "toDataURLWithQuality" or "setLineDash" 7360 * @return {Boolean | null} `true` if method is supported (or at least exists), 7361 * `null` if canvas element or context can not be initialized 7362 */ 7363 supports: function (methodName) { 7364 var el = fabric.util.createCanvasElement(); 7365 7366 if (!el || !el.getContext) { 7367 return null; 7368 } 7369 7370 var ctx = el.getContext('2d'); 7371 if (!ctx) { 7372 return null; 7373 } 7374 7375 switch (methodName) { 7376 7377 case 'getImageData': 7378 return typeof ctx.getImageData !== 'undefined'; 7379 7380 case 'setLineDash': 7381 return typeof ctx.setLineDash !== 'undefined'; 7382 7383 case 'toDataURL': 7384 return typeof el.toDataURL !== 'undefined'; 7385 7386 case 'toDataURLWithQuality': 7387 try { 7388 el.toDataURL('image/jpeg', 0); 7389 return true; 7390 } 7391 catch (e) { } 7392 return false; 7393 7394 default: 7395 return null; 7396 } 7397 } 7398 }); 7399 7400 /** 7401 * Returns JSON representation of canvas 7402 * @function 7403 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 7404 * @return {String} JSON string 7405 * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#serialization} 7406 * @see {@link http://jsfiddle.net/fabricjs/pec86/|jsFiddle demo} 7407 * @example <caption>JSON without additional properties</caption> 7408 * var json = canvas.toJSON(); 7409 * @example <caption>JSON with additional properties included</caption> 7410 * var json = canvas.toJSON(['lockMovementX', 'lockMovementY', 'lockRotation', 'lockScalingX', 'lockScalingY', 'lockUniScaling']); 7411 * @example <caption>JSON without default values</caption> 7412 * canvas.includeDefaultValues = false; 7413 * var json = canvas.toJSON(); 7414 */ 7415 fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject; 7416 7417 })(); 7418 7419 7420 /** 7421 * BaseBrush class 7422 * @class fabric.BaseBrush 7423 * @see {@link http://fabricjs.com/freedrawing/|Freedrawing demo} 7424 */ 7425 fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype */ { 7426 7427 /** 7428 * Color of a brush 7429 * @type String 7430 * @default 7431 */ 7432 color: 'rgb(0, 0, 0)', 7433 7434 /** 7435 * Width of a brush 7436 * @type Number 7437 * @default 7438 */ 7439 width: 1, 7440 7441 /** 7442 * Shadow object representing shadow of this shape. 7443 * <b>Backwards incompatibility note:</b> This property replaces "shadowColor" (String), "shadowOffsetX" (Number), 7444 * "shadowOffsetY" (Number) and "shadowBlur" (Number) since v1.2.12 7445 * @type fabric.Shadow 7446 * @default 7447 */ 7448 shadow: null, 7449 7450 /** 7451 * Line endings style of a brush (one of "butt", "round", "square") 7452 * @type String 7453 * @default 7454 */ 7455 strokeLineCap: 'round', 7456 7457 /** 7458 * Corner style of a brush (one of "bevil", "round", "miter") 7459 * @type String 7460 * @default 7461 */ 7462 strokeLineJoin: 'round', 7463 7464 /** 7465 * Stroke Dash Array. 7466 * @type Array 7467 * @default 7468 */ 7469 strokeDashArray: null, 7470 7471 /** 7472 * Sets shadow of an object 7473 * @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)") 7474 * @return {fabric.Object} thisArg 7475 * @chainable 7476 */ 7477 setShadow: function(options) { 7478 this.shadow = new fabric.Shadow(options); 7479 return this; 7480 }, 7481 7482 /** 7483 * Sets brush styles 7484 * @private 7485 */ 7486 _setBrushStyles: function() { 7487 var ctx = this.canvas.contextTop; 7488 7489 ctx.strokeStyle = this.color; 7490 ctx.lineWidth = this.width; 7491 ctx.lineCap = this.strokeLineCap; 7492 ctx.lineJoin = this.strokeLineJoin; 7493 if (this.strokeDashArray && fabric.StaticCanvas.supports('setLineDash')) { 7494 ctx.setLineDash(this.strokeDashArray); 7495 } 7496 }, 7497 7498 /** 7499 * Sets brush shadow styles 7500 * @private 7501 */ 7502 _setShadow: function() { 7503 if (!this.shadow) { 7504 return; 7505 } 7506 7507 var ctx = this.canvas.contextTop; 7508 7509 ctx.shadowColor = this.shadow.color; 7510 ctx.shadowBlur = this.shadow.blur; 7511 ctx.shadowOffsetX = this.shadow.offsetX; 7512 ctx.shadowOffsetY = this.shadow.offsetY; 7513 }, 7514 7515 /** 7516 * Removes brush shadow styles 7517 * @private 7518 */ 7519 _resetShadow: function() { 7520 var ctx = this.canvas.contextTop; 7521 7522 ctx.shadowColor = ''; 7523 ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; 7524 } 7525 }); 7526 7527 7528 (function() { 7529 7530 /** 7531 * PencilBrush class 7532 * @class fabric.PencilBrush 7533 * @extends fabric.BaseBrush 7534 */ 7535 fabric.PencilBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.PencilBrush.prototype */ { 7536 7537 /** 7538 * Constructor 7539 * @param {fabric.Canvas} canvas 7540 * @return {fabric.PencilBrush} Instance of a pencil brush 7541 */ 7542 initialize: function(canvas) { 7543 this.canvas = canvas; 7544 this._points = [ ]; 7545 }, 7546 7547 /** 7548 * Inovoked on mouse down 7549 * @param {Object} pointer 7550 */ 7551 onMouseDown: function(pointer) { 7552 this._prepareForDrawing(pointer); 7553 // capture coordinates immediately 7554 // this allows to draw dots (when movement never occurs) 7555 this._captureDrawingPath(pointer); 7556 this._render(); 7557 }, 7558 7559 /** 7560 * Inovoked on mouse move 7561 * @param {Object} pointer 7562 */ 7563 onMouseMove: function(pointer) { 7564 this._captureDrawingPath(pointer); 7565 // redraw curve 7566 // clear top canvas 7567 this.canvas.clearContext(this.canvas.contextTop); 7568 this._render(); 7569 }, 7570 7571 /** 7572 * Invoked on mouse up 7573 */ 7574 onMouseUp: function() { 7575 this._finalizeAndAddPath(); 7576 }, 7577 7578 /** 7579 * @private 7580 * @param {Object} pointer Actual mouse position related to the canvas. 7581 */ 7582 _prepareForDrawing: function(pointer) { 7583 7584 var p = new fabric.Point(pointer.x, pointer.y); 7585 7586 this._reset(); 7587 this._addPoint(p); 7588 7589 this.canvas.contextTop.moveTo(p.x, p.y); 7590 }, 7591 7592 /** 7593 * @private 7594 * @param {fabric.Point} point Point to be added to points array 7595 */ 7596 _addPoint: function(point) { 7597 this._points.push(point); 7598 }, 7599 7600 /** 7601 * Clear points array and set contextTop canvas style. 7602 * @private 7603 */ 7604 _reset: function() { 7605 this._points.length = 0; 7606 7607 this._setBrushStyles(); 7608 this._setShadow(); 7609 }, 7610 7611 /** 7612 * @private 7613 * @param {Object} pointer Actual mouse position related to the canvas. 7614 */ 7615 _captureDrawingPath: function(pointer) { 7616 var pointerPoint = new fabric.Point(pointer.x, pointer.y); 7617 this._addPoint(pointerPoint); 7618 }, 7619 7620 /** 7621 * Draw a smooth path on the topCanvas using quadraticCurveTo 7622 * @private 7623 */ 7624 _render: function() { 7625 var ctx = this.canvas.contextTop, 7626 v = this.canvas.viewportTransform, 7627 p1 = this._points[0], 7628 p2 = this._points[1]; 7629 7630 ctx.save(); 7631 ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); 7632 ctx.beginPath(); 7633 7634 //if we only have 2 points in the path and they are the same 7635 //it means that the user only clicked the canvas without moving the mouse 7636 //then we should be drawing a dot. A path isn't drawn between two identical dots 7637 //that's why we set them apart a bit 7638 if (this._points.length === 2 && p1.x === p2.x && p1.y === p2.y) { 7639 p1.x -= 0.5; 7640 p2.x += 0.5; 7641 } 7642 ctx.moveTo(p1.x, p1.y); 7643 7644 for (var i = 1, len = this._points.length; i < len; i++) { 7645 // we pick the point between pi + 1 & pi + 2 as the 7646 // end point and p1 as our control point. 7647 var midPoint = p1.midPointFrom(p2); 7648 ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y); 7649 7650 p1 = this._points[i]; 7651 p2 = this._points[i + 1]; 7652 } 7653 // Draw last line as a straight line while 7654 // we wait for the next point to be able to calculate 7655 // the bezier control point 7656 ctx.lineTo(p1.x, p1.y); 7657 ctx.stroke(); 7658 ctx.restore(); 7659 }, 7660 7661 /** 7662 * Converts points to SVG path 7663 * @param {Array} points Array of points 7664 * @param {Number} minX 7665 * @param {Number} minY 7666 * @return {String} SVG path 7667 */ 7668 convertPointsToSVGPath: function(points) { 7669 var path = [], 7670 p1 = new fabric.Point(points[0].x, points[0].y), 7671 p2 = new fabric.Point(points[1].x, points[1].y); 7672 7673 path.push('M ', points[0].x, ' ', points[0].y, ' '); 7674 for (var i = 1, len = points.length; i < len; i++) { 7675 var midPoint = p1.midPointFrom(p2); 7676 // p1 is our bezier control point 7677 // midpoint is our endpoint 7678 // start point is p(i-1) value. 7679 path.push('Q ', p1.x, ' ', p1.y, ' ', midPoint.x, ' ', midPoint.y, ' '); 7680 p1 = new fabric.Point(points[i].x, points[i].y); 7681 if ((i + 1) < points.length) { 7682 p2 = new fabric.Point(points[i + 1].x, points[i + 1].y); 7683 } 7684 } 7685 path.push('L ', p1.x, ' ', p1.y, ' '); 7686 return path; 7687 }, 7688 7689 /** 7690 * Creates fabric.Path object to add on canvas 7691 * @param {String} pathData Path data 7692 * @return {fabric.Path} Path to add on canvas 7693 */ 7694 createPath: function(pathData) { 7695 var path = new fabric.Path(pathData, { 7696 fill: null, 7697 stroke: this.color, 7698 strokeWidth: this.width, 7699 strokeLineCap: this.strokeLineCap, 7700 strokeLineJoin: this.strokeLineJoin, 7701 strokeDashArray: this.strokeDashArray, 7702 originX: 'center', 7703 originY: 'center' 7704 }); 7705 7706 if (this.shadow) { 7707 this.shadow.affectStroke = true; 7708 path.setShadow(this.shadow); 7709 } 7710 7711 return path; 7712 }, 7713 7714 /** 7715 * On mouseup after drawing the path on contextTop canvas 7716 * we use the points captured to create an new fabric path object 7717 * and add it to the fabric canvas. 7718 */ 7719 _finalizeAndAddPath: function() { 7720 var ctx = this.canvas.contextTop; 7721 ctx.closePath(); 7722 7723 var pathData = this.convertPointsToSVGPath(this._points).join(''); 7724 if (pathData === 'M 0 0 Q 0 0 0 0 L 0 0') { 7725 // do not create 0 width/height paths, as they are 7726 // rendered inconsistently across browsers 7727 // Firefox 4, for example, renders a dot, 7728 // whereas Chrome 10 renders nothing 7729 this.canvas.renderAll(); 7730 return; 7731 } 7732 7733 var path = this.createPath(pathData); 7734 7735 this.canvas.add(path); 7736 path.setCoords(); 7737 7738 this.canvas.clearContext(this.canvas.contextTop); 7739 this._resetShadow(); 7740 this.canvas.renderAll(); 7741 7742 // fire event 'path' created 7743 this.canvas.fire('path:created', { path: path }); 7744 } 7745 }); 7746 })(); 7747 7748 7749 /** 7750 * CircleBrush class 7751 * @class fabric.CircleBrush 7752 */ 7753 fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.CircleBrush.prototype */ { 7754 7755 /** 7756 * Width of a brush 7757 * @type Number 7758 * @default 7759 */ 7760 width: 10, 7761 7762 /** 7763 * Constructor 7764 * @param {fabric.Canvas} canvas 7765 * @return {fabric.CircleBrush} Instance of a circle brush 7766 */ 7767 initialize: function(canvas) { 7768 this.canvas = canvas; 7769 this.points = [ ]; 7770 }, 7771 7772 /** 7773 * Invoked inside on mouse down and mouse move 7774 * @param {Object} pointer 7775 */ 7776 drawDot: function(pointer) { 7777 var point = this.addPoint(pointer), 7778 ctx = this.canvas.contextTop, 7779 v = this.canvas.viewportTransform; 7780 ctx.save(); 7781 ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); 7782 7783 ctx.fillStyle = point.fill; 7784 ctx.beginPath(); 7785 ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false); 7786 ctx.closePath(); 7787 ctx.fill(); 7788 7789 ctx.restore(); 7790 }, 7791 7792 /** 7793 * Invoked on mouse down 7794 */ 7795 onMouseDown: function(pointer) { 7796 this.points.length = 0; 7797 this.canvas.clearContext(this.canvas.contextTop); 7798 this._setShadow(); 7799 this.drawDot(pointer); 7800 }, 7801 7802 /** 7803 * Invoked on mouse move 7804 * @param {Object} pointer 7805 */ 7806 onMouseMove: function(pointer) { 7807 this.drawDot(pointer); 7808 }, 7809 7810 /** 7811 * Invoked on mouse up 7812 */ 7813 onMouseUp: function() { 7814 var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; 7815 this.canvas.renderOnAddRemove = false; 7816 7817 var circles = [ ]; 7818 7819 for (var i = 0, len = this.points.length; i < len; i++) { 7820 var point = this.points[i], 7821 circle = new fabric.Circle({ 7822 radius: point.radius, 7823 left: point.x, 7824 top: point.y, 7825 originX: 'center', 7826 originY: 'center', 7827 fill: point.fill 7828 }); 7829 7830 this.shadow && circle.setShadow(this.shadow); 7831 7832 circles.push(circle); 7833 } 7834 var group = new fabric.Group(circles, { originX: 'center', originY: 'center' }); 7835 group.canvas = this.canvas; 7836 7837 this.canvas.add(group); 7838 this.canvas.fire('path:created', { path: group }); 7839 7840 this.canvas.clearContext(this.canvas.contextTop); 7841 this._resetShadow(); 7842 this.canvas.renderOnAddRemove = originalRenderOnAddRemove; 7843 this.canvas.renderAll(); 7844 }, 7845 7846 /** 7847 * @param {Object} pointer 7848 * @return {fabric.Point} Just added pointer point 7849 */ 7850 addPoint: function(pointer) { 7851 var pointerPoint = new fabric.Point(pointer.x, pointer.y), 7852 7853 circleRadius = fabric.util.getRandomInt( 7854 Math.max(0, this.width - 20), this.width + 20) / 2, 7855 7856 circleColor = new fabric.Color(this.color) 7857 .setAlpha(fabric.util.getRandomInt(0, 100) / 100) 7858 .toRgba(); 7859 7860 pointerPoint.radius = circleRadius; 7861 pointerPoint.fill = circleColor; 7862 7863 this.points.push(pointerPoint); 7864 7865 return pointerPoint; 7866 } 7867 }); 7868 7869 7870 /** 7871 * SprayBrush class 7872 * @class fabric.SprayBrush 7873 */ 7874 fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric.SprayBrush.prototype */ { 7875 7876 /** 7877 * Width of a spray 7878 * @type Number 7879 * @default 7880 */ 7881 width: 10, 7882 7883 /** 7884 * Density of a spray (number of dots per chunk) 7885 * @type Number 7886 * @default 7887 */ 7888 density: 20, 7889 7890 /** 7891 * Width of spray dots 7892 * @type Number 7893 * @default 7894 */ 7895 dotWidth: 1, 7896 7897 /** 7898 * Width variance of spray dots 7899 * @type Number 7900 * @default 7901 */ 7902 dotWidthVariance: 1, 7903 7904 /** 7905 * Whether opacity of a dot should be random 7906 * @type Boolean 7907 * @default 7908 */ 7909 randomOpacity: false, 7910 7911 /** 7912 * Whether overlapping dots (rectangles) should be removed (for performance reasons) 7913 * @type Boolean 7914 * @default 7915 */ 7916 optimizeOverlapping: true, 7917 7918 /** 7919 * Constructor 7920 * @param {fabric.Canvas} canvas 7921 * @return {fabric.SprayBrush} Instance of a spray brush 7922 */ 7923 initialize: function(canvas) { 7924 this.canvas = canvas; 7925 this.sprayChunks = [ ]; 7926 }, 7927 7928 /** 7929 * Invoked on mouse down 7930 * @param {Object} pointer 7931 */ 7932 onMouseDown: function(pointer) { 7933 this.sprayChunks.length = 0; 7934 this.canvas.clearContext(this.canvas.contextTop); 7935 this._setShadow(); 7936 7937 this.addSprayChunk(pointer); 7938 this.render(); 7939 }, 7940 7941 /** 7942 * Invoked on mouse move 7943 * @param {Object} pointer 7944 */ 7945 onMouseMove: function(pointer) { 7946 this.addSprayChunk(pointer); 7947 this.render(); 7948 }, 7949 7950 /** 7951 * Invoked on mouse up 7952 */ 7953 onMouseUp: function() { 7954 var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; 7955 this.canvas.renderOnAddRemove = false; 7956 7957 var rects = [ ]; 7958 7959 for (var i = 0, ilen = this.sprayChunks.length; i < ilen; i++) { 7960 var sprayChunk = this.sprayChunks[i]; 7961 7962 for (var j = 0, jlen = sprayChunk.length; j < jlen; j++) { 7963 7964 var rect = new fabric.Rect({ 7965 width: sprayChunk[j].width, 7966 height: sprayChunk[j].width, 7967 left: sprayChunk[j].x + 1, 7968 top: sprayChunk[j].y + 1, 7969 originX: 'center', 7970 originY: 'center', 7971 fill: this.color 7972 }); 7973 7974 this.shadow && rect.setShadow(this.shadow); 7975 rects.push(rect); 7976 } 7977 } 7978 7979 if (this.optimizeOverlapping) { 7980 rects = this._getOptimizedRects(rects); 7981 } 7982 7983 var group = new fabric.Group(rects, { originX: 'center', originY: 'center' }); 7984 group.canvas = this.canvas; 7985 7986 this.canvas.add(group); 7987 this.canvas.fire('path:created', { path: group }); 7988 7989 this.canvas.clearContext(this.canvas.contextTop); 7990 this._resetShadow(); 7991 this.canvas.renderOnAddRemove = originalRenderOnAddRemove; 7992 this.canvas.renderAll(); 7993 }, 7994 7995 /** 7996 * @private 7997 * @param {Array} rects 7998 */ 7999 _getOptimizedRects: function(rects) { 8000 8001 // avoid creating duplicate rects at the same coordinates 8002 var uniqueRects = { }, key; 8003 8004 for (var i = 0, len = rects.length; i < len; i++) { 8005 key = rects[i].left + '' + rects[i].top; 8006 if (!uniqueRects[key]) { 8007 uniqueRects[key] = rects[i]; 8008 } 8009 } 8010 var uniqueRectsArray = [ ]; 8011 for (key in uniqueRects) { 8012 uniqueRectsArray.push(uniqueRects[key]); 8013 } 8014 8015 return uniqueRectsArray; 8016 }, 8017 8018 /** 8019 * Renders brush 8020 */ 8021 render: function() { 8022 var ctx = this.canvas.contextTop; 8023 ctx.fillStyle = this.color; 8024 8025 var v = this.canvas.viewportTransform; 8026 ctx.save(); 8027 ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); 8028 8029 for (var i = 0, len = this.sprayChunkPoints.length; i < len; i++) { 8030 var point = this.sprayChunkPoints[i]; 8031 if (typeof point.opacity !== 'undefined') { 8032 ctx.globalAlpha = point.opacity; 8033 } 8034 ctx.fillRect(point.x, point.y, point.width, point.width); 8035 } 8036 ctx.restore(); 8037 }, 8038 8039 /** 8040 * @param {Object} pointer 8041 */ 8042 addSprayChunk: function(pointer) { 8043 this.sprayChunkPoints = [ ]; 8044 8045 var x, y, width, radius = this.width / 2; 8046 8047 for (var i = 0; i < this.density; i++) { 8048 8049 x = fabric.util.getRandomInt(pointer.x - radius, pointer.x + radius); 8050 y = fabric.util.getRandomInt(pointer.y - radius, pointer.y + radius); 8051 8052 if (this.dotWidthVariance) { 8053 width = fabric.util.getRandomInt( 8054 // bottom clamp width to 1 8055 Math.max(1, this.dotWidth - this.dotWidthVariance), 8056 this.dotWidth + this.dotWidthVariance); 8057 } 8058 else { 8059 width = this.dotWidth; 8060 } 8061 8062 var point = new fabric.Point(x, y); 8063 point.width = width; 8064 8065 if (this.randomOpacity) { 8066 point.opacity = fabric.util.getRandomInt(0, 100) / 100; 8067 } 8068 8069 this.sprayChunkPoints.push(point); 8070 } 8071 8072 this.sprayChunks.push(this.sprayChunkPoints); 8073 } 8074 }); 8075 8076 8077 /** 8078 * PatternBrush class 8079 * @class fabric.PatternBrush 8080 * @extends fabric.BaseBrush 8081 */ 8082 fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fabric.PatternBrush.prototype */ { 8083 8084 getPatternSrc: function() { 8085 8086 var dotWidth = 20, 8087 dotDistance = 5, 8088 patternCanvas = fabric.document.createElement('canvas'), 8089 patternCtx = patternCanvas.getContext('2d'); 8090 8091 patternCanvas.width = patternCanvas.height = dotWidth + dotDistance; 8092 8093 patternCtx.fillStyle = this.color; 8094 patternCtx.beginPath(); 8095 patternCtx.arc(dotWidth / 2, dotWidth / 2, dotWidth / 2, 0, Math.PI * 2, false); 8096 patternCtx.closePath(); 8097 patternCtx.fill(); 8098 8099 return patternCanvas; 8100 }, 8101 8102 getPatternSrcFunction: function() { 8103 return String(this.getPatternSrc).replace('this.color', '"' + this.color + '"'); 8104 }, 8105 8106 /** 8107 * Creates "pattern" instance property 8108 */ 8109 getPattern: function() { 8110 return this.canvas.contextTop.createPattern(this.source || this.getPatternSrc(), 'repeat'); 8111 }, 8112 8113 /** 8114 * Sets brush styles 8115 */ 8116 _setBrushStyles: function() { 8117 this.callSuper('_setBrushStyles'); 8118 this.canvas.contextTop.strokeStyle = this.getPattern(); 8119 }, 8120 8121 /** 8122 * Creates path 8123 */ 8124 createPath: function(pathData) { 8125 var path = this.callSuper('createPath', pathData); 8126 path.stroke = new fabric.Pattern({ 8127 source: this.source || this.getPatternSrcFunction() 8128 }); 8129 return path; 8130 } 8131 }); 8132 8133 8134 (function() { 8135 8136 var getPointer = fabric.util.getPointer, 8137 degreesToRadians = fabric.util.degreesToRadians, 8138 radiansToDegrees = fabric.util.radiansToDegrees, 8139 atan2 = Math.atan2, 8140 abs = Math.abs, 8141 8142 STROKE_OFFSET = 0.5; 8143 8144 /** 8145 * Canvas class 8146 * @class fabric.Canvas 8147 * @extends fabric.StaticCanvas 8148 * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#canvas} 8149 * @see {@link fabric.Canvas#initialize} for constructor definition 8150 * 8151 * @fires object:modified 8152 * @fires object:rotating 8153 * @fires object:scaling 8154 * @fires object:moving 8155 * @fires object:selected 8156 * 8157 * @fires before:selection:cleared 8158 * @fires selection:cleared 8159 * @fires selection:created 8160 * 8161 * @fires path:created 8162 * @fires mouse:down 8163 * @fires mouse:move 8164 * @fires mouse:up 8165 * @fires mouse:over 8166 * @fires mouse:out 8167 * 8168 */ 8169 fabric.Canvas = fabric.util.createClass(fabric.StaticCanvas, /** @lends fabric.Canvas.prototype */ { 8170 8171 /** 8172 * Constructor 8173 * @param {HTMLElement | String} el <canvas> element to initialize instance on 8174 * @param {Object} [options] Options object 8175 * @return {Object} thisArg 8176 */ 8177 initialize: function(el, options) { 8178 options || (options = { }); 8179 8180 this._initStatic(el, options); 8181 this._initInteractive(); 8182 this._createCacheCanvas(); 8183 }, 8184 8185 /** 8186 * When true, objects can be transformed by one side (unproportionally) 8187 * @type Boolean 8188 * @default 8189 */ 8190 uniScaleTransform: false, 8191 8192 /** 8193 * When true, objects use center point as the origin of scale transformation. 8194 * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean). 8195 * @since 1.3.4 8196 * @type Boolean 8197 * @default 8198 */ 8199 centeredScaling: false, 8200 8201 /** 8202 * When true, objects use center point as the origin of rotate transformation. 8203 * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean). 8204 * @since 1.3.4 8205 * @type Boolean 8206 * @default 8207 */ 8208 centeredRotation: false, 8209 8210 /** 8211 * Indicates that canvas is interactive. This property should not be changed. 8212 * @type Boolean 8213 * @default 8214 */ 8215 interactive: true, 8216 8217 /** 8218 * Indicates whether group selection should be enabled 8219 * @type Boolean 8220 * @default 8221 */ 8222 selection: true, 8223 8224 /** 8225 * Color of selection 8226 * @type String 8227 * @default 8228 */ 8229 selectionColor: 'rgba(100, 100, 255, 0.3)', // blue 8230 8231 /** 8232 * Default dash array pattern 8233 * If not empty the selection border is dashed 8234 * @type Array 8235 */ 8236 selectionDashArray: [ ], 8237 8238 /** 8239 * Color of the border of selection (usually slightly darker than color of selection itself) 8240 * @type String 8241 * @default 8242 */ 8243 selectionBorderColor: 'rgba(255, 255, 255, 0.3)', 8244 8245 /** 8246 * Width of a line used in object/group selection 8247 * @type Number 8248 * @default 8249 */ 8250 selectionLineWidth: 1, 8251 8252 /** 8253 * Default cursor value used when hovering over an object on canvas 8254 * @type String 8255 * @default 8256 */ 8257 hoverCursor: 'move', 8258 8259 /** 8260 * Default cursor value used when moving an object on canvas 8261 * @type String 8262 * @default 8263 */ 8264 moveCursor: 'move', 8265 8266 /** 8267 * Default cursor value used for the entire canvas 8268 * @type String 8269 * @default 8270 */ 8271 defaultCursor: 'default', 8272 8273 /** 8274 * Cursor value used during free drawing 8275 * @type String 8276 * @default 8277 */ 8278 freeDrawingCursor: 'crosshair', 8279 8280 /** 8281 * Cursor value used for rotation point 8282 * @type String 8283 * @default 8284 */ 8285 rotationCursor: 'crosshair', 8286 8287 /** 8288 * Default element class that's given to wrapper (div) element of canvas 8289 * @type String 8290 * @default 8291 */ 8292 containerClass: 'canvas-container', 8293 8294 /** 8295 * When true, object detection happens on per-pixel basis rather than on per-bounding-box 8296 * @type Boolean 8297 * @default 8298 */ 8299 perPixelTargetFind: false, 8300 8301 /** 8302 * Number of pixels around target pixel to tolerate (consider active) during object detection 8303 * @type Number 8304 * @default 8305 */ 8306 targetFindTolerance: 0, 8307 8308 /** 8309 * When true, target detection is skipped when hovering over canvas. This can be used to improve performance. 8310 * @type Boolean 8311 * @default 8312 */ 8313 skipTargetFind: false, 8314 8315 /** 8316 * When true, mouse events on canvas (mousedown/mousemove/mouseup) result in free drawing. 8317 * After mousedown, mousemove creates a shape, 8318 * and then mouseup finalizes it and adds an instance of `fabric.Path` onto canvas. 8319 * @tutorial {@link http://fabricjs.com/fabric-intro-part-4/#free_drawing} 8320 * @type Boolean 8321 * @default 8322 */ 8323 isDrawingMode: false, 8324 8325 /** 8326 * @private 8327 */ 8328 _initInteractive: function() { 8329 this._currentTransform = null; 8330 this._groupSelector = null; 8331 this._initWrapperElement(); 8332 this._createUpperCanvas(); 8333 this._initEventListeners(); 8334 8335 this._initRetinaScaling(); 8336 8337 this.freeDrawingBrush = fabric.PencilBrush && new fabric.PencilBrush(this); 8338 8339 this.calcOffset(); 8340 }, 8341 8342 /** 8343 * Resets the current transform to its original values and chooses the type of resizing based on the event 8344 * @private 8345 * @param {Event} e Event object fired on mousemove 8346 */ 8347 _resetCurrentTransform: function() { 8348 var t = this._currentTransform; 8349 8350 t.target.set({ 8351 scaleX: t.original.scaleX, 8352 scaleY: t.original.scaleY, 8353 skewX: t.original.skewX, 8354 skewY: t.original.skewY, 8355 left: t.original.left, 8356 top: t.original.top 8357 }); 8358 8359 if (this._shouldCenterTransform(t.target)) { 8360 if (t.action === 'rotate') { 8361 this._setOriginToCenter(t.target); 8362 } 8363 else { 8364 if (t.originX !== 'center') { 8365 if (t.originX === 'right') { 8366 t.mouseXSign = -1; 8367 } 8368 else { 8369 t.mouseXSign = 1; 8370 } 8371 } 8372 if (t.originY !== 'center') { 8373 if (t.originY === 'bottom') { 8374 t.mouseYSign = -1; 8375 } 8376 else { 8377 t.mouseYSign = 1; 8378 } 8379 } 8380 8381 t.originX = 'center'; 8382 t.originY = 'center'; 8383 } 8384 } 8385 else { 8386 t.originX = t.original.originX; 8387 t.originY = t.original.originY; 8388 } 8389 }, 8390 8391 /** 8392 * Checks if point is contained within an area of given object 8393 * @param {Event} e Event object 8394 * @param {fabric.Object} target Object to test against 8395 * @return {Boolean} true if point is contained within an area of given object 8396 */ 8397 containsPoint: function (e, target) { 8398 var pointer = this.getPointer(e, true), 8399 xy = this._normalizePointer(target, pointer); 8400 8401 // http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html 8402 // http://idav.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html 8403 return (target.containsPoint(xy) || target._findTargetCorner(pointer)); 8404 }, 8405 8406 /** 8407 * @private 8408 */ 8409 _normalizePointer: function (object, pointer) { 8410 var activeGroup = this.getActiveGroup(), 8411 isObjectInGroup = ( 8412 activeGroup && 8413 object.type !== 'group' && 8414 activeGroup.contains(object)), 8415 lt, m; 8416 8417 if (isObjectInGroup) { 8418 m = fabric.util.multiplyTransformMatrices( 8419 this.viewportTransform, 8420 activeGroup.calcTransformMatrix()); 8421 8422 m = fabric.util.invertTransform(m); 8423 pointer = fabric.util.transformPoint(pointer, m , false); 8424 lt = fabric.util.transformPoint(activeGroup.getCenterPoint(), m , false); 8425 pointer.x -= lt.x; 8426 pointer.y -= lt.y; 8427 } 8428 return { x: pointer.x, y: pointer.y }; 8429 }, 8430 8431 /** 8432 * Returns true if object is transparent at a certain location 8433 * @param {fabric.Object} target Object to check 8434 * @param {Number} x Left coordinate 8435 * @param {Number} y Top coordinate 8436 * @return {Boolean} 8437 */ 8438 isTargetTransparent: function (target, x, y) { 8439 var hasBorders = target.hasBorders, 8440 transparentCorners = target.transparentCorners; 8441 8442 target.hasBorders = target.transparentCorners = false; 8443 8444 target.render(this.contextCache); 8445 target._renderControls(this.contextCache); 8446 8447 target.hasBorders = hasBorders; 8448 target.transparentCorners = transparentCorners; 8449 8450 var isTransparent = fabric.util.isTransparent( 8451 this.contextCache, x, y, this.targetFindTolerance); 8452 8453 this.clearContext(this.contextCache); 8454 8455 return isTransparent; 8456 }, 8457 8458 /** 8459 * @private 8460 * @param {Event} e Event object 8461 * @param {fabric.Object} target 8462 */ 8463 _shouldClearSelection: function (e, target) { 8464 var activeGroup = this.getActiveGroup(), 8465 activeObject = this.getActiveObject(); 8466 8467 return ( 8468 !target 8469 || 8470 (target && 8471 activeGroup && 8472 !activeGroup.contains(target) && 8473 activeGroup !== target && 8474 !e.shiftKey) 8475 || 8476 (target && !target.evented) 8477 || 8478 (target && 8479 !target.selectable && 8480 activeObject && 8481 activeObject !== target) 8482 ); 8483 }, 8484 8485 /** 8486 * @private 8487 * @param {fabric.Object} target 8488 */ 8489 _shouldCenterTransform: function (target) { 8490 if (!target) { 8491 return; 8492 } 8493 8494 var t = this._currentTransform, 8495 centerTransform; 8496 8497 if (t.action === 'scale' || t.action === 'scaleX' || t.action === 'scaleY') { 8498 centerTransform = this.centeredScaling || target.centeredScaling; 8499 } 8500 else if (t.action === 'rotate') { 8501 centerTransform = this.centeredRotation || target.centeredRotation; 8502 } 8503 8504 return centerTransform ? !t.altKey : t.altKey; 8505 }, 8506 8507 /** 8508 * @private 8509 */ 8510 _getOriginFromCorner: function(target, corner) { 8511 var origin = { 8512 x: target.originX, 8513 y: target.originY 8514 }; 8515 8516 if (corner === 'ml' || corner === 'tl' || corner === 'bl') { 8517 origin.x = 'right'; 8518 } 8519 else if (corner === 'mr' || corner === 'tr' || corner === 'br') { 8520 origin.x = 'left'; 8521 } 8522 8523 if (corner === 'tl' || corner === 'mt' || corner === 'tr') { 8524 origin.y = 'bottom'; 8525 } 8526 else if (corner === 'bl' || corner === 'mb' || corner === 'br') { 8527 origin.y = 'top'; 8528 } 8529 8530 return origin; 8531 }, 8532 8533 /** 8534 * @private 8535 */ 8536 _getActionFromCorner: function(target, corner, e) { 8537 if (!corner) { 8538 return 'drag'; 8539 } 8540 8541 switch (corner) { 8542 case 'mtr': 8543 return 'rotate'; 8544 case 'ml': 8545 case 'mr': 8546 return e.shiftKey ? 'skewY' : 'scaleX'; 8547 case 'mt': 8548 case 'mb': 8549 return e.shiftKey ? 'skewX' : 'scaleY'; 8550 default: 8551 return 'scale'; 8552 } 8553 }, 8554 8555 /** 8556 * @private 8557 * @param {Event} e Event object 8558 * @param {fabric.Object} target 8559 */ 8560 _setupCurrentTransform: function (e, target) { 8561 if (!target) { 8562 return; 8563 } 8564 8565 var pointer = this.getPointer(e), 8566 corner = target._findTargetCorner(this.getPointer(e, true)), 8567 action = this._getActionFromCorner(target, corner, e), 8568 origin = this._getOriginFromCorner(target, corner); 8569 8570 this._currentTransform = { 8571 target: target, 8572 action: action, 8573 corner: corner, 8574 scaleX: target.scaleX, 8575 scaleY: target.scaleY, 8576 skewX: target.skewX, 8577 skewY: target.skewY, 8578 offsetX: pointer.x - target.left, 8579 offsetY: pointer.y - target.top, 8580 originX: origin.x, 8581 originY: origin.y, 8582 ex: pointer.x, 8583 ey: pointer.y, 8584 lastX: pointer.x, 8585 lastY: pointer.y, 8586 left: target.left, 8587 top: target.top, 8588 theta: degreesToRadians(target.angle), 8589 width: target.width * target.scaleX, 8590 mouseXSign: 1, 8591 mouseYSign: 1, 8592 shiftKey: e.shiftKey, 8593 altKey: e.altKey 8594 }; 8595 8596 this._currentTransform.original = { 8597 left: target.left, 8598 top: target.top, 8599 scaleX: target.scaleX, 8600 scaleY: target.scaleY, 8601 skewX: target.skewX, 8602 skewY: target.skewY, 8603 originX: origin.x, 8604 originY: origin.y 8605 }; 8606 8607 this._resetCurrentTransform(); 8608 }, 8609 8610 /** 8611 * Translates object by "setting" its left/top 8612 * @private 8613 * @param {Number} x pointer's x coordinate 8614 * @param {Number} y pointer's y coordinate 8615 */ 8616 _translateObject: function (x, y) { 8617 var target = this._currentTransform.target; 8618 8619 if (!target.get('lockMovementX')) { 8620 target.set('left', x - this._currentTransform.offsetX); 8621 } 8622 if (!target.get('lockMovementY')) { 8623 target.set('top', y - this._currentTransform.offsetY); 8624 } 8625 }, 8626 8627 /** 8628 * Check if we are increasing a positive skew or lower it, 8629 * checking mouse direction and pressed corner. 8630 * @private 8631 */ 8632 _changeSkewTransformOrigin: function(mouseMove, t, by) { 8633 var property = 'originX', origins = { 0: 'center' }, 8634 skew = t.target.skewX, originA = 'left', originB = 'right', 8635 corner = t.corner === 'mt' || t.corner === 'ml' ? 1 : -1, 8636 flipSign = 1; 8637 8638 mouseMove = mouseMove > 0 ? 1 : -1; 8639 if (by === 'y') { 8640 skew = t.target.skewY; 8641 originA = 'top'; 8642 originB = 'bottom'; 8643 property = 'originY'; 8644 } 8645 origins[-1] = originA; 8646 origins[1] = originB; 8647 8648 t.target.flipX && (flipSign *= -1); 8649 t.target.flipY && (flipSign *= -1); 8650 8651 if (skew === 0) { 8652 t.skewSign = -corner * mouseMove * flipSign; 8653 t[property] = origins[-mouseMove]; 8654 } 8655 else { 8656 skew = skew > 0 ? 1 : -1; 8657 t.skewSign = skew; 8658 t[property] = origins[skew * corner * flipSign]; 8659 } 8660 }, 8661 8662 /** 8663 * Skew object by mouse events 8664 * @private 8665 * @param {Number} x pointer's x coordinate 8666 * @param {Number} y pointer's y coordinate 8667 * @param {String} by Either 'x' or 'y' 8668 */ 8669 _skewObject: function (x, y, by) { 8670 var t = this._currentTransform, 8671 target = t.target, 8672 lockSkewingX = target.get('lockSkewingX'), 8673 lockSkewingY = target.get('lockSkewingY'); 8674 8675 if ((lockSkewingX && by === 'x') || (lockSkewingY && by === 'y')) { 8676 return; 8677 } 8678 8679 // Get the constraint point 8680 var center = target.getCenterPoint(), 8681 actualMouseByCenter = target.toLocalPoint(new fabric.Point(x, y), 'center', 'center')[by], 8682 lastMouseByCenter = target.toLocalPoint(new fabric.Point(t.lastX, t.lastY), 'center', 'center')[by], 8683 actualMouseByOrigin, constraintPosition, dim = target._getTransformedDimensions(); 8684 8685 this._changeSkewTransformOrigin(actualMouseByCenter - lastMouseByCenter, t, by); 8686 actualMouseByOrigin = target.toLocalPoint(new fabric.Point(x, y), t.originX, t.originY)[by], 8687 8688 constraintPosition = target.translateToOriginPoint(center, t.originX, t.originY); 8689 // Actually skew the object 8690 this._setObjectSkew(actualMouseByOrigin, t, by, dim); 8691 t.lastX = x; 8692 t.lastY = y; 8693 // Make sure the constraints apply 8694 target.setPositionByOrigin(constraintPosition, t.originX, t.originY); 8695 }, 8696 8697 _setObjectSkew: function(localMouse, transform, by, _dim) { 8698 var target = transform.target, newValue, 8699 skewSign = transform.skewSign, newDim, dimNoSkew, 8700 otherBy, _otherBy, _by, newDimMouse, skewX, skewY; 8701 8702 if (by === 'x') { 8703 otherBy = 'y'; 8704 _otherBy = 'Y'; 8705 _by = 'X'; 8706 skewX = 0; 8707 skewY = target.skewY; 8708 } 8709 else { 8710 otherBy = 'x'; 8711 _otherBy = 'X'; 8712 _by = 'Y'; 8713 skewX = target.skewX; 8714 skewY = 0; 8715 } 8716 8717 dimNoSkew = target._getTransformedDimensions(skewX, skewY); 8718 newDimMouse = 2 * Math.abs(localMouse) - dimNoSkew[by]; 8719 if (newDimMouse <= 2) { 8720 newValue = 0; 8721 } 8722 else { 8723 newValue = skewSign * Math.atan((newDimMouse / target['scale' + _by]) / 8724 (dimNoSkew[otherBy] / target['scale' + _otherBy])); 8725 newValue = fabric.util.radiansToDegrees(newValue); 8726 } 8727 target.set('skew' + _by, newValue); 8728 if (target['skew' + _otherBy] !== 0) { 8729 newDim = target._getTransformedDimensions(); 8730 newValue = (_dim[otherBy] / newDim[otherBy]) * target['scale' + _otherBy]; 8731 target.set('scale' + _otherBy, newValue); 8732 } 8733 }, 8734 8735 /** 8736 * Scales object by invoking its scaleX/scaleY methods 8737 * @private 8738 * @param {Number} x pointer's x coordinate 8739 * @param {Number} y pointer's y coordinate 8740 * @param {String} by Either 'x' or 'y' - specifies dimension constraint by which to scale an object. 8741 * When not provided, an object is scaled by both dimensions equally 8742 */ 8743 _scaleObject: function (x, y, by) { 8744 var t = this._currentTransform, 8745 target = t.target, 8746 lockScalingX = target.get('lockScalingX'), 8747 lockScalingY = target.get('lockScalingY'), 8748 lockScalingFlip = target.get('lockScalingFlip'); 8749 8750 if (lockScalingX && lockScalingY) { 8751 return; 8752 } 8753 8754 // Get the constraint point 8755 var constraintPosition = target.translateToOriginPoint(target.getCenterPoint(), t.originX, t.originY), 8756 localMouse = target.toLocalPoint(new fabric.Point(x, y), t.originX, t.originY), 8757 dim = target._getTransformedDimensions(); 8758 8759 this._setLocalMouse(localMouse, t); 8760 8761 // Actually scale the object 8762 this._setObjectScale(localMouse, t, lockScalingX, lockScalingY, by, lockScalingFlip, dim); 8763 8764 // Make sure the constraints apply 8765 target.setPositionByOrigin(constraintPosition, t.originX, t.originY); 8766 }, 8767 8768 /** 8769 * @private 8770 */ 8771 _setObjectScale: function(localMouse, transform, lockScalingX, lockScalingY, by, lockScalingFlip, _dim) { 8772 var target = transform.target, forbidScalingX = false, forbidScalingY = false; 8773 8774 transform.newScaleX = localMouse.x * target.scaleX / _dim.x; 8775 transform.newScaleY = localMouse.y * target.scaleY / _dim.y; 8776 8777 if (lockScalingFlip && transform.newScaleX <= 0 && transform.newScaleX < target.scaleX) { 8778 forbidScalingX = true; 8779 } 8780 8781 if (lockScalingFlip && transform.newScaleY <= 0 && transform.newScaleY < target.scaleY) { 8782 forbidScalingY = true; 8783 } 8784 8785 if (by === 'equally' && !lockScalingX && !lockScalingY) { 8786 forbidScalingX || forbidScalingY || this._scaleObjectEqually(localMouse, target, transform, _dim); 8787 } 8788 else if (!by) { 8789 forbidScalingX || lockScalingX || target.set('scaleX', transform.newScaleX); 8790 forbidScalingY || lockScalingY || target.set('scaleY', transform.newScaleY); 8791 } 8792 else if (by === 'x' && !target.get('lockUniScaling')) { 8793 forbidScalingX || lockScalingX || target.set('scaleX', transform.newScaleX); 8794 } 8795 else if (by === 'y' && !target.get('lockUniScaling')) { 8796 forbidScalingY || lockScalingY || target.set('scaleY', transform.newScaleY); 8797 } 8798 8799 forbidScalingX || forbidScalingY || this._flipObject(transform, by); 8800 8801 }, 8802 8803 /** 8804 * @private 8805 */ 8806 _scaleObjectEqually: function(localMouse, target, transform, _dim) { 8807 8808 var dist = localMouse.y + localMouse.x, 8809 lastDist = _dim.y * transform.original.scaleY / target.scaleY + 8810 _dim.x * transform.original.scaleX / target.scaleX; 8811 8812 // We use transform.scaleX/Y instead of target.scaleX/Y 8813 // because the object may have a min scale and we'll loose the proportions 8814 transform.newScaleX = transform.original.scaleX * dist / lastDist; 8815 transform.newScaleY = transform.original.scaleY * dist / lastDist; 8816 8817 target.set('scaleX', transform.newScaleX); 8818 target.set('scaleY', transform.newScaleY); 8819 }, 8820 8821 /** 8822 * @private 8823 */ 8824 _flipObject: function(transform, by) { 8825 if (transform.newScaleX < 0 && by !== 'y') { 8826 if (transform.originX === 'left') { 8827 transform.originX = 'right'; 8828 } 8829 else if (transform.originX === 'right') { 8830 transform.originX = 'left'; 8831 } 8832 } 8833 8834 if (transform.newScaleY < 0 && by !== 'x') { 8835 if (transform.originY === 'top') { 8836 transform.originY = 'bottom'; 8837 } 8838 else if (transform.originY === 'bottom') { 8839 transform.originY = 'top'; 8840 } 8841 } 8842 }, 8843 8844 /** 8845 * @private 8846 */ 8847 _setLocalMouse: function(localMouse, t) { 8848 var target = t.target; 8849 8850 if (t.originX === 'right') { 8851 localMouse.x *= -1; 8852 } 8853 else if (t.originX === 'center') { 8854 localMouse.x *= t.mouseXSign * 2; 8855 8856 if (localMouse.x < 0) { 8857 t.mouseXSign = -t.mouseXSign; 8858 } 8859 } 8860 8861 if (t.originY === 'bottom') { 8862 localMouse.y *= -1; 8863 } 8864 else if (t.originY === 'center') { 8865 localMouse.y *= t.mouseYSign * 2; 8866 8867 if (localMouse.y < 0) { 8868 t.mouseYSign = -t.mouseYSign; 8869 } 8870 } 8871 8872 // adjust the mouse coordinates when dealing with padding 8873 if (abs(localMouse.x) > target.padding) { 8874 if (localMouse.x < 0) { 8875 localMouse.x += target.padding; 8876 } 8877 else { 8878 localMouse.x -= target.padding; 8879 } 8880 } 8881 else { // mouse is within the padding, set to 0 8882 localMouse.x = 0; 8883 } 8884 8885 if (abs(localMouse.y) > target.padding) { 8886 if (localMouse.y < 0) { 8887 localMouse.y += target.padding; 8888 } 8889 else { 8890 localMouse.y -= target.padding; 8891 } 8892 } 8893 else { 8894 localMouse.y = 0; 8895 } 8896 }, 8897 8898 /** 8899 * Rotates object by invoking its rotate method 8900 * @private 8901 * @param {Number} x pointer's x coordinate 8902 * @param {Number} y pointer's y coordinate 8903 */ 8904 _rotateObject: function (x, y) { 8905 8906 var t = this._currentTransform; 8907 8908 if (t.target.get('lockRotation')) { 8909 return; 8910 } 8911 8912 var lastAngle = atan2(t.ey - t.top, t.ex - t.left), 8913 curAngle = atan2(y - t.top, x - t.left), 8914 angle = radiansToDegrees(curAngle - lastAngle + t.theta); 8915 8916 // normalize angle to positive value 8917 if (angle < 0) { 8918 angle = 360 + angle; 8919 } 8920 8921 t.target.angle = angle % 360; 8922 }, 8923 8924 /** 8925 * Set the cursor type of the canvas element 8926 * @param {String} value Cursor type of the canvas element. 8927 * @see http://www.w3.org/TR/css3-ui/#cursor 8928 */ 8929 setCursor: function (value) { 8930 this.upperCanvasEl.style.cursor = value; 8931 }, 8932 8933 /** 8934 * @private 8935 */ 8936 _resetObjectTransform: function (target) { 8937 target.scaleX = 1; 8938 target.scaleY = 1; 8939 target.skewX = 0; 8940 target.skewY = 0; 8941 target.setAngle(0); 8942 }, 8943 8944 /** 8945 * @private 8946 */ 8947 _drawSelection: function () { 8948 var ctx = this.contextTop, 8949 groupSelector = this._groupSelector, 8950 left = groupSelector.left, 8951 top = groupSelector.top, 8952 aleft = abs(left), 8953 atop = abs(top); 8954 8955 ctx.fillStyle = this.selectionColor; 8956 8957 ctx.fillRect( 8958 groupSelector.ex - ((left > 0) ? 0 : -left), 8959 groupSelector.ey - ((top > 0) ? 0 : -top), 8960 aleft, 8961 atop 8962 ); 8963 8964 ctx.lineWidth = this.selectionLineWidth; 8965 ctx.strokeStyle = this.selectionBorderColor; 8966 8967 // selection border 8968 if (this.selectionDashArray.length > 1) { 8969 8970 var px = groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0: aleft), 8971 py = groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0: atop); 8972 8973 ctx.beginPath(); 8974 8975 fabric.util.drawDashedLine(ctx, px, py, px + aleft, py, this.selectionDashArray); 8976 fabric.util.drawDashedLine(ctx, px, py + atop - 1, px + aleft, py + atop - 1, this.selectionDashArray); 8977 fabric.util.drawDashedLine(ctx, px, py, px, py + atop, this.selectionDashArray); 8978 fabric.util.drawDashedLine(ctx, px + aleft - 1, py, px + aleft - 1, py + atop, this.selectionDashArray); 8979 8980 ctx.closePath(); 8981 ctx.stroke(); 8982 } 8983 else { 8984 ctx.strokeRect( 8985 groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0 : aleft), 8986 groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0 : atop), 8987 aleft, 8988 atop 8989 ); 8990 } 8991 }, 8992 8993 /** 8994 * @private 8995 */ 8996 _isLastRenderedObject: function(e) { 8997 return ( 8998 this.controlsAboveOverlay && 8999 this.lastRenderedObjectWithControlsAboveOverlay && 9000 this.lastRenderedObjectWithControlsAboveOverlay.visible && 9001 this.containsPoint(e, this.lastRenderedObjectWithControlsAboveOverlay) && 9002 this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(this.getPointer(e, true))); 9003 }, 9004 9005 /** 9006 * Method that determines what object we are clicking on 9007 * @param {Event} e mouse event 9008 * @param {Boolean} skipGroup when true, group is skipped and only objects are traversed through 9009 */ 9010 findTarget: function (e, skipGroup) { 9011 if (this.skipTargetFind) { 9012 return; 9013 } 9014 9015 if (this._isLastRenderedObject(e)) { 9016 return this.lastRenderedObjectWithControlsAboveOverlay; 9017 } 9018 9019 // first check current group (if one exists) 9020 var activeGroup = this.getActiveGroup(); 9021 if (activeGroup && !skipGroup && this.containsPoint(e, activeGroup)) { 9022 return activeGroup; 9023 } 9024 9025 var target = this._searchPossibleTargets(e, skipGroup); 9026 this._fireOverOutEvents(target, e); 9027 9028 return target; 9029 }, 9030 9031 /** 9032 * @private 9033 */ 9034 _fireOverOutEvents: function(target, e) { 9035 if (target) { 9036 if (this._hoveredTarget !== target) { 9037 if (this._hoveredTarget) { 9038 this.fire('mouse:out', { target: this._hoveredTarget, e: e }); 9039 this._hoveredTarget.fire('mouseout'); 9040 } 9041 this.fire('mouse:over', { target: target, e: e }); 9042 target.fire('mouseover'); 9043 this._hoveredTarget = target; 9044 } 9045 } 9046 else if (this._hoveredTarget) { 9047 this.fire('mouse:out', { target: this._hoveredTarget, e: e }); 9048 this._hoveredTarget.fire('mouseout'); 9049 this._hoveredTarget = null; 9050 } 9051 }, 9052 9053 /** 9054 * @private 9055 */ 9056 _checkTarget: function(e, obj, pointer) { 9057 if (obj && 9058 obj.visible && 9059 obj.evented && 9060 this.containsPoint(e, obj)){ 9061 if ((this.perPixelTargetFind || obj.perPixelTargetFind) && !obj.isEditing) { 9062 var isTransparent = this.isTargetTransparent(obj, pointer.x, pointer.y); 9063 if (!isTransparent) { 9064 return true; 9065 } 9066 } 9067 else { 9068 return true; 9069 } 9070 } 9071 }, 9072 9073 /** 9074 * @private 9075 */ 9076 _searchPossibleTargets: function(e, skipGroup) { 9077 9078 // Cache all targets where their bounding box contains point. 9079 var target, 9080 pointer = this.getPointer(e, true), 9081 i = this._objects.length; 9082 // Do not check for currently grouped objects, since we check the parent group itself. 9083 // untill we call this function specifically to search inside the activeGroup 9084 while (i--) { 9085 if ((!this._objects[i].group || skipGroup) && this._checkTarget(e, this._objects[i], pointer)){ 9086 this.relatedTarget = this._objects[i]; 9087 target = this._objects[i]; 9088 break; 9089 } 9090 } 9091 9092 return target; 9093 }, 9094 9095 /** 9096 * Returns pointer coordinates relative to canvas. 9097 * @param {Event} e 9098 * @return {Object} object with "x" and "y" number values 9099 */ 9100 getPointer: function (e, ignoreZoom, upperCanvasEl) { 9101 if (!upperCanvasEl) { 9102 upperCanvasEl = this.upperCanvasEl; 9103 } 9104 var pointer = getPointer(e), 9105 bounds = upperCanvasEl.getBoundingClientRect(), 9106 boundsWidth = bounds.width || 0, 9107 boundsHeight = bounds.height || 0, 9108 cssScale; 9109 9110 if (!boundsWidth || !boundsHeight ) { 9111 if ('top' in bounds && 'bottom' in bounds) { 9112 boundsHeight = Math.abs( bounds.top - bounds.bottom ); 9113 } 9114 if ('right' in bounds && 'left' in bounds) { 9115 boundsWidth = Math.abs( bounds.right - bounds.left ); 9116 } 9117 } 9118 9119 this.calcOffset(); 9120 9121 pointer.x = pointer.x - this._offset.left; 9122 pointer.y = pointer.y - this._offset.top; 9123 if (!ignoreZoom) { 9124 pointer = fabric.util.transformPoint( 9125 pointer, 9126 fabric.util.invertTransform(this.viewportTransform) 9127 ); 9128 } 9129 9130 if (boundsWidth === 0 || boundsHeight === 0) { 9131 // If bounds are not available (i.e. not visible), do not apply scale. 9132 cssScale = { width: 1, height: 1 }; 9133 } 9134 else { 9135 cssScale = { 9136 width: upperCanvasEl.width / boundsWidth, 9137 height: upperCanvasEl.height / boundsHeight 9138 }; 9139 } 9140 9141 return { 9142 x: pointer.x * cssScale.width, 9143 y: pointer.y * cssScale.height 9144 }; 9145 }, 9146 9147 /** 9148 * @private 9149 * @throws {CANVAS_INIT_ERROR} If canvas can not be initialized 9150 */ 9151 _createUpperCanvas: function () { 9152 var lowerCanvasClass = this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/, ''); 9153 9154 this.upperCanvasEl = this._createCanvasElement(); 9155 fabric.util.addClass(this.upperCanvasEl, 'upper-canvas ' + lowerCanvasClass); 9156 9157 this.wrapperEl.appendChild(this.upperCanvasEl); 9158 9159 this._copyCanvasStyle(this.lowerCanvasEl, this.upperCanvasEl); 9160 this._applyCanvasStyle(this.upperCanvasEl); 9161 this.contextTop = this.upperCanvasEl.getContext('2d'); 9162 }, 9163 9164 /** 9165 * @private 9166 */ 9167 _createCacheCanvas: function () { 9168 this.cacheCanvasEl = this._createCanvasElement(); 9169 this.cacheCanvasEl.setAttribute('width', this.width); 9170 this.cacheCanvasEl.setAttribute('height', this.height); 9171 this.contextCache = this.cacheCanvasEl.getContext('2d'); 9172 }, 9173 9174 /** 9175 * @private 9176 */ 9177 _initWrapperElement: function () { 9178 this.wrapperEl = fabric.util.wrapElement(this.lowerCanvasEl, 'div', { 9179 'class': this.containerClass 9180 }); 9181 fabric.util.setStyle(this.wrapperEl, { 9182 width: this.getWidth() + 'px', 9183 height: this.getHeight() + 'px', 9184 position: 'relative' 9185 }); 9186 fabric.util.makeElementUnselectable(this.wrapperEl); 9187 }, 9188 9189 /** 9190 * @private 9191 * @param {HTMLElement} element canvas element to apply styles on 9192 */ 9193 _applyCanvasStyle: function (element) { 9194 var width = this.getWidth() || element.width, 9195 height = this.getHeight() || element.height; 9196 9197 fabric.util.setStyle(element, { 9198 position: 'absolute', 9199 width: width + 'px', 9200 height: height + 'px', 9201 left: 0, 9202 top: 0 9203 }); 9204 element.width = width; 9205 element.height = height; 9206 fabric.util.makeElementUnselectable(element); 9207 }, 9208 9209 /** 9210 * Copys the the entire inline style from one element (fromEl) to another (toEl) 9211 * @private 9212 * @param {Element} fromEl Element style is copied from 9213 * @param {Element} toEl Element copied style is applied to 9214 */ 9215 _copyCanvasStyle: function (fromEl, toEl) { 9216 toEl.style.cssText = fromEl.style.cssText; 9217 }, 9218 9219 /** 9220 * Returns context of canvas where object selection is drawn 9221 * @return {CanvasRenderingContext2D} 9222 */ 9223 getSelectionContext: function() { 9224 return this.contextTop; 9225 }, 9226 9227 /** 9228 * Returns <canvas> element on which object selection is drawn 9229 * @return {HTMLCanvasElement} 9230 */ 9231 getSelectionElement: function () { 9232 return this.upperCanvasEl; 9233 }, 9234 9235 /** 9236 * @private 9237 * @param {Object} object 9238 */ 9239 _setActiveObject: function(object) { 9240 if (this._activeObject) { 9241 this._activeObject.set('active', false); 9242 } 9243 this._activeObject = object; 9244 object.set('active', true); 9245 }, 9246 9247 /** 9248 * Sets given object as the only active object on canvas 9249 * @param {fabric.Object} object Object to set as an active one 9250 * @param {Event} [e] Event (passed along when firing "object:selected") 9251 * @return {fabric.Canvas} thisArg 9252 * @chainable 9253 */ 9254 setActiveObject: function (object, e) { 9255 this._setActiveObject(object); 9256 this.renderAll(); 9257 this.fire('object:selected', { target: object, e: e }); 9258 object.fire('selected', { e: e }); 9259 return this; 9260 }, 9261 9262 /** 9263 * Returns currently active object 9264 * @return {fabric.Object} active object 9265 */ 9266 getActiveObject: function () { 9267 return this._activeObject; 9268 }, 9269 9270 /** 9271 * @private 9272 */ 9273 _discardActiveObject: function() { 9274 if (this._activeObject) { 9275 this._activeObject.set('active', false); 9276 } 9277 this._activeObject = null; 9278 }, 9279 9280 /** 9281 * Discards currently active object 9282 * @return {fabric.Canvas} thisArg 9283 * @chainable 9284 */ 9285 discardActiveObject: function (e) { 9286 this._discardActiveObject(); 9287 this.renderAll(); 9288 this.fire('selection:cleared', { e: e }); 9289 return this; 9290 }, 9291 9292 /** 9293 * @private 9294 * @param {fabric.Group} group 9295 */ 9296 _setActiveGroup: function(group) { 9297 this._activeGroup = group; 9298 if (group) { 9299 group.set('active', true); 9300 } 9301 }, 9302 9303 /** 9304 * Sets active group to a specified one 9305 * @param {fabric.Group} group Group to set as a current one 9306 * @return {fabric.Canvas} thisArg 9307 * @chainable 9308 */ 9309 setActiveGroup: function (group, e) { 9310 this._setActiveGroup(group); 9311 if (group) { 9312 this.fire('object:selected', { target: group, e: e }); 9313 group.fire('selected', { e: e }); 9314 } 9315 return this; 9316 }, 9317 9318 /** 9319 * Returns currently active group 9320 * @return {fabric.Group} Current group 9321 */ 9322 getActiveGroup: function () { 9323 return this._activeGroup; 9324 }, 9325 9326 /** 9327 * @private 9328 */ 9329 _discardActiveGroup: function() { 9330 var g = this.getActiveGroup(); 9331 if (g) { 9332 g.destroy(); 9333 } 9334 this.setActiveGroup(null); 9335 }, 9336 9337 /** 9338 * Discards currently active group 9339 * @return {fabric.Canvas} thisArg 9340 */ 9341 discardActiveGroup: function (e) { 9342 this._discardActiveGroup(); 9343 this.fire('selection:cleared', { e: e }); 9344 return this; 9345 }, 9346 9347 /** 9348 * Deactivates all objects on canvas, removing any active group or object 9349 * @return {fabric.Canvas} thisArg 9350 */ 9351 deactivateAll: function () { 9352 var allObjects = this.getObjects(), 9353 i = 0, 9354 len = allObjects.length; 9355 for ( ; i < len; i++) { 9356 allObjects[i].set('active', false); 9357 } 9358 this._discardActiveGroup(); 9359 this._discardActiveObject(); 9360 return this; 9361 }, 9362 9363 /** 9364 * Deactivates all objects and dispatches appropriate events 9365 * @return {fabric.Canvas} thisArg 9366 */ 9367 deactivateAllWithDispatch: function (e) { 9368 var activeObject = this.getActiveGroup() || this.getActiveObject(); 9369 if (activeObject) { 9370 this.fire('before:selection:cleared', { target: activeObject, e: e }); 9371 } 9372 this.deactivateAll(); 9373 if (activeObject) { 9374 this.fire('selection:cleared', { e: e }); 9375 } 9376 return this; 9377 }, 9378 9379 /** 9380 * Clears a canvas element and removes all event listeners 9381 * @return {fabric.Canvas} thisArg 9382 * @chainable 9383 */ 9384 dispose: function () { 9385 this.callSuper('dispose'); 9386 var wrapper = this.wrapperEl; 9387 this.removeListeners(); 9388 wrapper.removeChild(this.upperCanvasEl); 9389 wrapper.removeChild(this.lowerCanvasEl); 9390 delete this.upperCanvasEl; 9391 if (wrapper.parentNode) { 9392 wrapper.parentNode.replaceChild(this.lowerCanvasEl, this.wrapperEl); 9393 } 9394 delete this.wrapperEl; 9395 return this; 9396 }, 9397 9398 /** 9399 * Draws objects' controls (borders/controls) 9400 * @param {CanvasRenderingContext2D} ctx Context to render controls on 9401 */ 9402 drawControls: function(ctx) { 9403 var activeGroup = this.getActiveGroup(); 9404 9405 if (activeGroup) { 9406 activeGroup._renderControls(ctx); 9407 } 9408 else { 9409 this._drawObjectsControls(ctx); 9410 } 9411 }, 9412 9413 /** 9414 * @private 9415 */ 9416 _drawObjectsControls: function(ctx) { 9417 for (var i = 0, len = this._objects.length; i < len; ++i) { 9418 if (!this._objects[i] || !this._objects[i].active) { 9419 continue; 9420 } 9421 this._objects[i]._renderControls(ctx); 9422 this.lastRenderedObjectWithControlsAboveOverlay = this._objects[i]; 9423 } 9424 } 9425 }); 9426 9427 // copying static properties manually to work around Opera's bug, 9428 // where "prototype" property is enumerable and overrides existing prototype 9429 for (var prop in fabric.StaticCanvas) { 9430 if (prop !== 'prototype') { 9431 fabric.Canvas[prop] = fabric.StaticCanvas[prop]; 9432 } 9433 } 9434 9435 if (fabric.isTouchSupported) { 9436 /** @ignore */ 9437 fabric.Canvas.prototype._setCursorFromEvent = function() { }; 9438 } 9439 9440 /** 9441 * @class fabric.Element 9442 * @alias fabric.Canvas 9443 * @deprecated Use {@link fabric.Canvas} instead. 9444 * @constructor 9445 */ 9446 fabric.Element = fabric.Canvas; 9447 })(); 9448 9449 9450 (function() { 9451 9452 var cursorOffset = { 9453 mt: 0, // n 9454 tr: 1, // ne 9455 mr: 2, // e 9456 br: 3, // se 9457 mb: 4, // s 9458 bl: 5, // sw 9459 ml: 6, // w 9460 tl: 7 // nw 9461 }, 9462 addListener = fabric.util.addListener, 9463 removeListener = fabric.util.removeListener; 9464 9465 fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ { 9466 9467 /** 9468 * Map of cursor style values for each of the object controls 9469 * @private 9470 */ 9471 cursorMap: [ 9472 'n-resize', 9473 'ne-resize', 9474 'e-resize', 9475 'se-resize', 9476 's-resize', 9477 'sw-resize', 9478 'w-resize', 9479 'nw-resize' 9480 ], 9481 9482 /** 9483 * Adds mouse listeners to canvas 9484 * @private 9485 */ 9486 _initEventListeners: function () { 9487 9488 this._bindEvents(); 9489 9490 addListener(fabric.window, 'resize', this._onResize); 9491 9492 // mouse events 9493 addListener(this.upperCanvasEl, 'mousedown', this._onMouseDown); 9494 addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); 9495 addListener(this.upperCanvasEl, 'mousewheel', this._onMouseWheel); 9496 9497 // touch events 9498 addListener(this.upperCanvasEl, 'touchstart', this._onMouseDown); 9499 addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); 9500 9501 if (typeof eventjs !== 'undefined' && 'add' in eventjs) { 9502 eventjs.add(this.upperCanvasEl, 'gesture', this._onGesture); 9503 eventjs.add(this.upperCanvasEl, 'drag', this._onDrag); 9504 eventjs.add(this.upperCanvasEl, 'orientation', this._onOrientationChange); 9505 eventjs.add(this.upperCanvasEl, 'shake', this._onShake); 9506 eventjs.add(this.upperCanvasEl, 'longpress', this._onLongPress); 9507 } 9508 }, 9509 9510 /** 9511 * @private 9512 */ 9513 _bindEvents: function() { 9514 this._onMouseDown = this._onMouseDown.bind(this); 9515 this._onMouseMove = this._onMouseMove.bind(this); 9516 this._onMouseUp = this._onMouseUp.bind(this); 9517 this._onResize = this._onResize.bind(this); 9518 this._onGesture = this._onGesture.bind(this); 9519 this._onDrag = this._onDrag.bind(this); 9520 this._onShake = this._onShake.bind(this); 9521 this._onLongPress = this._onLongPress.bind(this); 9522 this._onOrientationChange = this._onOrientationChange.bind(this); 9523 this._onMouseWheel = this._onMouseWheel.bind(this); 9524 }, 9525 9526 /** 9527 * Removes all event listeners 9528 */ 9529 removeListeners: function() { 9530 removeListener(fabric.window, 'resize', this._onResize); 9531 9532 removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown); 9533 removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); 9534 removeListener(this.upperCanvasEl, 'mousewheel', this._onMouseWheel); 9535 9536 removeListener(this.upperCanvasEl, 'touchstart', this._onMouseDown); 9537 removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); 9538 9539 if (typeof eventjs !== 'undefined' && 'remove' in eventjs) { 9540 eventjs.remove(this.upperCanvasEl, 'gesture', this._onGesture); 9541 eventjs.remove(this.upperCanvasEl, 'drag', this._onDrag); 9542 eventjs.remove(this.upperCanvasEl, 'orientation', this._onOrientationChange); 9543 eventjs.remove(this.upperCanvasEl, 'shake', this._onShake); 9544 eventjs.remove(this.upperCanvasEl, 'longpress', this._onLongPress); 9545 } 9546 }, 9547 9548 /** 9549 * @private 9550 * @param {Event} [e] Event object fired on Event.js gesture 9551 * @param {Event} [self] Inner Event object 9552 */ 9553 _onGesture: function(e, self) { 9554 this.__onTransformGesture && this.__onTransformGesture(e, self); 9555 }, 9556 9557 /** 9558 * @private 9559 * @param {Event} [e] Event object fired on Event.js drag 9560 * @param {Event} [self] Inner Event object 9561 */ 9562 _onDrag: function(e, self) { 9563 this.__onDrag && this.__onDrag(e, self); 9564 }, 9565 9566 /** 9567 * @private 9568 * @param {Event} [e] Event object fired on Event.js wheel event 9569 * @param {Event} [self] Inner Event object 9570 */ 9571 _onMouseWheel: function(e, self) { 9572 this.__onMouseWheel && this.__onMouseWheel(e, self); 9573 }, 9574 9575 /** 9576 * @private 9577 * @param {Event} [e] Event object fired on Event.js orientation change 9578 * @param {Event} [self] Inner Event object 9579 */ 9580 _onOrientationChange: function(e, self) { 9581 this.__onOrientationChange && this.__onOrientationChange(e, self); 9582 }, 9583 9584 /** 9585 * @private 9586 * @param {Event} [e] Event object fired on Event.js shake 9587 * @param {Event} [self] Inner Event object 9588 */ 9589 _onShake: function(e, self) { 9590 this.__onShake && this.__onShake(e, self); 9591 }, 9592 9593 /** 9594 * @private 9595 * @param {Event} [e] Event object fired on Event.js shake 9596 * @param {Event} [self] Inner Event object 9597 */ 9598 _onLongPress: function(e, self) { 9599 this.__onLongPress && this.__onLongPress(e, self); 9600 }, 9601 9602 /** 9603 * @private 9604 * @param {Event} e Event object fired on mousedown 9605 */ 9606 _onMouseDown: function (e) { 9607 this.__onMouseDown(e); 9608 9609 addListener(fabric.document, 'touchend', this._onMouseUp); 9610 addListener(fabric.document, 'touchmove', this._onMouseMove); 9611 9612 removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); 9613 removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); 9614 9615 if (e.type === 'touchstart') { 9616 // Unbind mousedown to prevent double triggers from touch devices 9617 removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown); 9618 } 9619 else { 9620 addListener(fabric.document, 'mouseup', this._onMouseUp); 9621 addListener(fabric.document, 'mousemove', this._onMouseMove); 9622 } 9623 }, 9624 9625 /** 9626 * @private 9627 * @param {Event} e Event object fired on mouseup 9628 */ 9629 _onMouseUp: function (e) { 9630 this.__onMouseUp(e); 9631 9632 removeListener(fabric.document, 'mouseup', this._onMouseUp); 9633 removeListener(fabric.document, 'touchend', this._onMouseUp); 9634 9635 removeListener(fabric.document, 'mousemove', this._onMouseMove); 9636 removeListener(fabric.document, 'touchmove', this._onMouseMove); 9637 9638 addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); 9639 addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); 9640 9641 if (e.type === 'touchend') { 9642 // Wait 400ms before rebinding mousedown to prevent double triggers 9643 // from touch devices 9644 var _this = this; 9645 setTimeout(function() { 9646 addListener(_this.upperCanvasEl, 'mousedown', _this._onMouseDown); 9647 }, 400); 9648 } 9649 }, 9650 9651 /** 9652 * @private 9653 * @param {Event} e Event object fired on mousemove 9654 */ 9655 _onMouseMove: function (e) { 9656 !this.allowTouchScrolling && e.preventDefault && e.preventDefault(); 9657 this.__onMouseMove(e); 9658 }, 9659 9660 /** 9661 * @private 9662 */ 9663 _onResize: function () { 9664 this.calcOffset(); 9665 }, 9666 9667 /** 9668 * Decides whether the canvas should be redrawn in mouseup and mousedown events. 9669 * @private 9670 * @param {Object} target 9671 * @param {Object} pointer 9672 */ 9673 _shouldRender: function(target, pointer) { 9674 var activeObject = this.getActiveGroup() || this.getActiveObject(); 9675 9676 return !!( 9677 (target && ( 9678 target.isMoving || 9679 target !== activeObject)) 9680 || 9681 (!target && !!activeObject) 9682 || 9683 (!target && !activeObject && !this._groupSelector) 9684 || 9685 (pointer && 9686 this._previousPointer && 9687 this.selection && ( 9688 pointer.x !== this._previousPointer.x || 9689 pointer.y !== this._previousPointer.y)) 9690 ); 9691 }, 9692 9693 /** 9694 * Method that defines the actions when mouse is released on canvas. 9695 * The method resets the currentTransform parameters, store the image corner 9696 * position in the image object and render the canvas on top. 9697 * @private 9698 * @param {Event} e Event object fired on mouseup 9699 */ 9700 __onMouseUp: function (e) { 9701 var target; 9702 9703 if (this.isDrawingMode && this._isCurrentlyDrawing) { 9704 this._onMouseUpInDrawingMode(e); 9705 return; 9706 } 9707 9708 if (this._currentTransform) { 9709 this._finalizeCurrentTransform(); 9710 target = this._currentTransform.target; 9711 } 9712 else { 9713 target = this.findTarget(e, true); 9714 } 9715 9716 var shouldRender = this._shouldRender(target, this.getPointer(e)); 9717 9718 this._maybeGroupObjects(e); 9719 9720 if (target) { 9721 target.isMoving = false; 9722 } 9723 9724 shouldRender && this.renderAll(); 9725 9726 this._handleCursorAndEvent(e, target); 9727 }, 9728 9729 _handleCursorAndEvent: function(e, target) { 9730 this._setCursorFromEvent(e, target); 9731 9732 // TODO: why are we doing this? 9733 var _this = this; 9734 setTimeout(function () { 9735 _this._setCursorFromEvent(e, target); 9736 }, 50); 9737 9738 this.fire('mouse:up', { target: target, e: e }); 9739 target && target.fire('mouseup', { e: e }); 9740 }, 9741 9742 /** 9743 * @private 9744 */ 9745 _finalizeCurrentTransform: function() { 9746 9747 var transform = this._currentTransform, 9748 target = transform.target; 9749 9750 if (target._scaling) { 9751 target._scaling = false; 9752 } 9753 9754 target.setCoords(); 9755 9756 // only fire :modified event if target coordinates were changed during mousedown-mouseup 9757 if (this.stateful && target.hasStateChanged()) { 9758 this.fire('object:modified', { target: target }); 9759 target.fire('modified'); 9760 } 9761 9762 this._restoreOriginXY(target); 9763 }, 9764 9765 /** 9766 * @private 9767 * @param {Object} target Object to restore 9768 */ 9769 _restoreOriginXY: function(target) { 9770 if (this._previousOriginX && this._previousOriginY) { 9771 9772 var originPoint = target.translateToOriginPoint( 9773 target.getCenterPoint(), 9774 this._previousOriginX, 9775 this._previousOriginY); 9776 9777 target.originX = this._previousOriginX; 9778 target.originY = this._previousOriginY; 9779 9780 target.left = originPoint.x; 9781 target.top = originPoint.y; 9782 9783 this._previousOriginX = null; 9784 this._previousOriginY = null; 9785 } 9786 }, 9787 9788 /** 9789 * @private 9790 * @param {Event} e Event object fired on mousedown 9791 */ 9792 _onMouseDownInDrawingMode: function(e) { 9793 this._isCurrentlyDrawing = true; 9794 this.discardActiveObject(e).renderAll(); 9795 if (this.clipTo) { 9796 fabric.util.clipContext(this, this.contextTop); 9797 } 9798 var ivt = fabric.util.invertTransform(this.viewportTransform), 9799 pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt); 9800 this.freeDrawingBrush.onMouseDown(pointer); 9801 this.fire('mouse:down', { e: e }); 9802 9803 var target = this.findTarget(e); 9804 if (typeof target !== 'undefined') { 9805 target.fire('mousedown', { e: e, target: target }); 9806 } 9807 }, 9808 9809 /** 9810 * @private 9811 * @param {Event} e Event object fired on mousemove 9812 */ 9813 _onMouseMoveInDrawingMode: function(e) { 9814 if (this._isCurrentlyDrawing) { 9815 var ivt = fabric.util.invertTransform(this.viewportTransform), 9816 pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt); 9817 this.freeDrawingBrush.onMouseMove(pointer); 9818 } 9819 this.setCursor(this.freeDrawingCursor); 9820 this.fire('mouse:move', { e: e }); 9821 9822 var target = this.findTarget(e); 9823 if (typeof target !== 'undefined') { 9824 target.fire('mousemove', { e: e, target: target }); 9825 } 9826 }, 9827 9828 /** 9829 * @private 9830 * @param {Event} e Event object fired on mouseup 9831 */ 9832 _onMouseUpInDrawingMode: function(e) { 9833 this._isCurrentlyDrawing = false; 9834 if (this.clipTo) { 9835 this.contextTop.restore(); 9836 } 9837 this.freeDrawingBrush.onMouseUp(); 9838 this.fire('mouse:up', { e: e }); 9839 9840 var target = this.findTarget(e); 9841 if (typeof target !== 'undefined') { 9842 target.fire('mouseup', { e: e, target: target }); 9843 } 9844 }, 9845 9846 /** 9847 * Method that defines the actions when mouse is clic ked on canvas. 9848 * The method inits the currentTransform parameters and renders all the 9849 * canvas so the current image can be placed on the top canvas and the rest 9850 * in on the container one. 9851 * @private 9852 * @param {Event} e Event object fired on mousedown 9853 */ 9854 __onMouseDown: function (e) { 9855 9856 // accept only left clicks 9857 var isLeftClick = 'which' in e ? e.which === 1 : e.button === 0; 9858 if (!isLeftClick && !fabric.isTouchSupported) { 9859 return; 9860 } 9861 9862 if (this.isDrawingMode) { 9863 this._onMouseDownInDrawingMode(e); 9864 return; 9865 } 9866 9867 // ignore if some object is being transformed at this moment 9868 if (this._currentTransform) { 9869 return; 9870 } 9871 9872 var target = this.findTarget(e), 9873 pointer = this.getPointer(e, true); 9874 9875 // save pointer for check in __onMouseUp event 9876 this._previousPointer = pointer; 9877 9878 var shouldRender = this._shouldRender(target, pointer), 9879 shouldGroup = this._shouldGroup(e, target); 9880 9881 if (this._shouldClearSelection(e, target)) { 9882 this._clearSelection(e, target, pointer); 9883 } 9884 else if (shouldGroup) { 9885 this._handleGrouping(e, target); 9886 target = this.getActiveGroup(); 9887 } 9888 9889 if (target && target.selectable && (target.__corner || !shouldGroup)) { 9890 this._beforeTransform(e, target); 9891 this._setupCurrentTransform(e, target); 9892 } 9893 // we must renderAll so that active image is placed on the top canvas 9894 shouldRender && this.renderAll(); 9895 9896 this.fire('mouse:down', { target: target, e: e }); 9897 target && target.fire('mousedown', { e: e }); 9898 }, 9899 9900 /** 9901 * @private 9902 */ 9903 _beforeTransform: function(e, target) { 9904 this.stateful && target.saveState(); 9905 9906 // determine if it's a drag or rotate case 9907 if (target._findTargetCorner(this.getPointer(e))) { 9908 this.onBeforeScaleRotate(target); 9909 } 9910 9911 if (target !== this.getActiveGroup() && target !== this.getActiveObject()) { 9912 this.deactivateAll(); 9913 this.setActiveObject(target, e); 9914 } 9915 }, 9916 9917 /** 9918 * @private 9919 */ 9920 _clearSelection: function(e, target, pointer) { 9921 this.deactivateAllWithDispatch(e); 9922 9923 if (target && target.selectable) { 9924 this.setActiveObject(target, e); 9925 } 9926 else if (this.selection) { 9927 this._groupSelector = { 9928 ex: pointer.x, 9929 ey: pointer.y, 9930 top: 0, 9931 left: 0 9932 }; 9933 } 9934 }, 9935 9936 /** 9937 * @private 9938 * @param {Object} target Object for that origin is set to center 9939 */ 9940 _setOriginToCenter: function(target) { 9941 this._previousOriginX = this._currentTransform.target.originX; 9942 this._previousOriginY = this._currentTransform.target.originY; 9943 9944 var center = target.getCenterPoint(); 9945 9946 target.originX = 'center'; 9947 target.originY = 'center'; 9948 9949 target.left = center.x; 9950 target.top = center.y; 9951 9952 this._currentTransform.left = target.left; 9953 this._currentTransform.top = target.top; 9954 }, 9955 9956 /** 9957 * @private 9958 * @param {Object} target Object for that center is set to origin 9959 */ 9960 _setCenterToOrigin: function(target) { 9961 var originPoint = target.translateToOriginPoint( 9962 target.getCenterPoint(), 9963 this._previousOriginX, 9964 this._previousOriginY); 9965 9966 target.originX = this._previousOriginX; 9967 target.originY = this._previousOriginY; 9968 9969 target.left = originPoint.x; 9970 target.top = originPoint.y; 9971 9972 this._previousOriginX = null; 9973 this._previousOriginY = null; 9974 }, 9975 9976 /** 9977 * Method that defines the actions when mouse is hovering the canvas. 9978 * The currentTransform parameter will definde whether the user is rotating/scaling/translating 9979 * an image or neither of them (only hovering). A group selection is also possible and would cancel 9980 * all any other type of action. 9981 * In case of an image transformation only the top canvas will be rendered. 9982 * @private 9983 * @param {Event} e Event object fired on mousemove 9984 */ 9985 __onMouseMove: function (e) { 9986 9987 var target, pointer; 9988 9989 if (this.isDrawingMode) { 9990 this._onMouseMoveInDrawingMode(e); 9991 return; 9992 } 9993 if (typeof e.touches !== 'undefined' && e.touches.length > 1) { 9994 return; 9995 } 9996 9997 var groupSelector = this._groupSelector; 9998 9999 // We initially clicked in an empty area, so we draw a box for multiple selection 10000 if (groupSelector) { 10001 pointer = this.getPointer(e, true); 10002 10003 groupSelector.left = pointer.x - groupSelector.ex; 10004 groupSelector.top = pointer.y - groupSelector.ey; 10005 10006 this.renderTop(); 10007 } 10008 else if (!this._currentTransform) { 10009 10010 target = this.findTarget(e); 10011 10012 if (!target || target && !target.selectable) { 10013 this.setCursor(this.defaultCursor); 10014 } 10015 else { 10016 this._setCursorFromEvent(e, target); 10017 } 10018 } 10019 else { 10020 this._transformObject(e); 10021 } 10022 10023 this.fire('mouse:move', { target: target, e: e }); 10024 target && target.fire('mousemove', { e: e }); 10025 }, 10026 10027 /** 10028 * @private 10029 * @param {Event} e Event fired on mousemove 10030 */ 10031 _transformObject: function(e) { 10032 var pointer = this.getPointer(e), 10033 transform = this._currentTransform; 10034 10035 transform.reset = false, 10036 transform.target.isMoving = true; 10037 10038 this._beforeScaleTransform(e, transform); 10039 this._performTransformAction(e, transform, pointer); 10040 10041 this.renderAll(); 10042 }, 10043 10044 /** 10045 * @private 10046 */ 10047 _performTransformAction: function(e, transform, pointer) { 10048 var x = pointer.x, 10049 y = pointer.y, 10050 target = transform.target, 10051 action = transform.action; 10052 10053 if (action === 'rotate') { 10054 this._rotateObject(x, y); 10055 this._fire('rotating', target, e); 10056 } 10057 else if (action === 'scale') { 10058 this._onScale(e, transform, x, y); 10059 this._fire('scaling', target, e); 10060 } 10061 else if (action === 'scaleX') { 10062 this._scaleObject(x, y, 'x'); 10063 this._fire('scaling', target, e); 10064 } 10065 else if (action === 'scaleY') { 10066 this._scaleObject(x, y, 'y'); 10067 this._fire('scaling', target, e); 10068 } 10069 else if (action === 'skewX') { 10070 this._skewObject(x, y, 'x'); 10071 this._fire('skewing', target, e); 10072 } 10073 else if (action === 'skewY') { 10074 this._skewObject(x, y, 'y'); 10075 this._fire('skewing', target, e); 10076 } 10077 else { 10078 this._translateObject(x, y); 10079 this._fire('moving', target, e); 10080 this.setCursor(this.moveCursor); 10081 } 10082 }, 10083 10084 /** 10085 * @private 10086 */ 10087 _fire: function(eventName, target, e) { 10088 this.fire('object:' + eventName, { target: target, e: e }); 10089 target.fire(eventName, { e: e }); 10090 }, 10091 10092 /** 10093 * @private 10094 */ 10095 _beforeScaleTransform: function(e, transform) { 10096 if (transform.action === 'scale' || transform.action === 'scaleX' || transform.action === 'scaleY') { 10097 var centerTransform = this._shouldCenterTransform(transform.target); 10098 10099 // Switch from a normal resize to center-based 10100 if ((centerTransform && (transform.originX !== 'center' || transform.originY !== 'center')) || 10101 // Switch from center-based resize to normal one 10102 (!centerTransform && transform.originX === 'center' && transform.originY === 'center') 10103 ) { 10104 this._resetCurrentTransform(); 10105 transform.reset = true; 10106 } 10107 } 10108 }, 10109 10110 /** 10111 * @private 10112 */ 10113 _onScale: function(e, transform, x, y) { 10114 // rotate object only if shift key is not pressed 10115 // and if it is not a group we are transforming 10116 if ((e.shiftKey || this.uniScaleTransform) && !transform.target.get('lockUniScaling')) { 10117 transform.currentAction = 'scale'; 10118 this._scaleObject(x, y); 10119 } 10120 else { 10121 // Switch from a normal resize to proportional 10122 if (!transform.reset && transform.currentAction === 'scale') { 10123 this._resetCurrentTransform(); 10124 } 10125 10126 transform.currentAction = 'scaleEqually'; 10127 this._scaleObject(x, y, 'equally'); 10128 } 10129 }, 10130 10131 /** 10132 * Sets the cursor depending on where the canvas is being hovered. 10133 * Note: very buggy in Opera 10134 * @param {Event} e Event object 10135 * @param {Object} target Object that the mouse is hovering, if so. 10136 */ 10137 _setCursorFromEvent: function (e, target) { 10138 if (!target || !target.selectable) { 10139 this.setCursor(this.defaultCursor); 10140 return false; 10141 } 10142 else { 10143 var activeGroup = this.getActiveGroup(), 10144 // only show proper corner when group selection is not active 10145 corner = target._findTargetCorner 10146 && (!activeGroup || !activeGroup.contains(target)) 10147 && target._findTargetCorner(this.getPointer(e, true)); 10148 10149 if (!corner) { 10150 this.setCursor(target.hoverCursor || this.hoverCursor); 10151 } 10152 else { 10153 this._setCornerCursor(corner, target, e); 10154 } 10155 } 10156 return true; 10157 }, 10158 10159 /** 10160 * @private 10161 */ 10162 _setCornerCursor: function(corner, target, e) { 10163 if (corner in cursorOffset) { 10164 this.setCursor(this._getRotatedCornerCursor(corner, target, e)); 10165 } 10166 else if (corner === 'mtr' && target.hasRotatingPoint) { 10167 this.setCursor(this.rotationCursor); 10168 } 10169 else { 10170 this.setCursor(this.defaultCursor); 10171 return false; 10172 } 10173 }, 10174 10175 /** 10176 * @private 10177 */ 10178 _getRotatedCornerCursor: function(corner, target, e) { 10179 var n = Math.round((target.getAngle() % 360) / 45); 10180 10181 if (n < 0) { 10182 n += 8; // full circle ahead 10183 } 10184 n += cursorOffset[corner]; 10185 if (e.shiftKey && cursorOffset[corner] % 2 === 0) { 10186 //if we are holding shift and we are on a mx corner... 10187 n += 2; 10188 } 10189 // normalize n to be from 0 to 7 10190 n %= 8; 10191 10192 return this.cursorMap[n]; 10193 } 10194 }); 10195 })(); 10196 10197 10198 (function() { 10199 10200 var min = Math.min, 10201 max = Math.max; 10202 10203 fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ { 10204 10205 /** 10206 * @private 10207 * @param {Event} e Event object 10208 * @param {fabric.Object} target 10209 * @return {Boolean} 10210 */ 10211 _shouldGroup: function(e, target) { 10212 var activeObject = this.getActiveObject(); 10213 return e.shiftKey && target && target.selectable && 10214 (this.getActiveGroup() || (activeObject && activeObject !== target)) 10215 && this.selection; 10216 }, 10217 10218 /** 10219 * @private 10220 * @param {Event} e Event object 10221 * @param {fabric.Object} target 10222 */ 10223 _handleGrouping: function (e, target) { 10224 10225 if (target === this.getActiveGroup()) { 10226 10227 // if it's a group, find target again, this time skipping group 10228 target = this.findTarget(e, true); 10229 10230 // if even object is not found, bail out 10231 if (!target || target.isType('group')) { 10232 return; 10233 } 10234 } 10235 if (this.getActiveGroup()) { 10236 this._updateActiveGroup(target, e); 10237 } 10238 else { 10239 this._createActiveGroup(target, e); 10240 } 10241 10242 if (this._activeGroup) { 10243 this._activeGroup.saveCoords(); 10244 } 10245 }, 10246 10247 /** 10248 * @private 10249 */ 10250 _updateActiveGroup: function(target, e) { 10251 var activeGroup = this.getActiveGroup(); 10252 10253 if (activeGroup.contains(target)) { 10254 10255 activeGroup.removeWithUpdate(target); 10256 target.set('active', false); 10257 10258 if (activeGroup.size() === 1) { 10259 // remove group alltogether if after removal it only contains 1 object 10260 this.discardActiveGroup(e); 10261 // activate last remaining object 10262 this.setActiveObject(activeGroup.item(0)); 10263 return; 10264 } 10265 } 10266 else { 10267 activeGroup.addWithUpdate(target); 10268 } 10269 this.fire('selection:created', { target: activeGroup, e: e }); 10270 activeGroup.set('active', true); 10271 }, 10272 10273 /** 10274 * @private 10275 */ 10276 _createActiveGroup: function(target, e) { 10277 10278 if (this._activeObject && target !== this._activeObject) { 10279 10280 var group = this._createGroup(target); 10281 group.addWithUpdate(); 10282 10283 this.setActiveGroup(group); 10284 this._activeObject = null; 10285 10286 this.fire('selection:created', { target: group, e: e }); 10287 } 10288 10289 target.set('active', true); 10290 }, 10291 10292 /** 10293 * @private 10294 * @param {Object} target 10295 */ 10296 _createGroup: function(target) { 10297 10298 var objects = this.getObjects(), 10299 isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target), 10300 groupObjects = isActiveLower 10301 ? [ this._activeObject, target ] 10302 : [ target, this._activeObject ]; 10303 10304 return new fabric.Group(groupObjects, { 10305 canvas: this 10306 }); 10307 }, 10308 10309 /** 10310 * @private 10311 * @param {Event} e mouse event 10312 */ 10313 _groupSelectedObjects: function (e) { 10314 10315 var group = this._collectObjects(); 10316 10317 // do not create group for 1 element only 10318 if (group.length === 1) { 10319 this.setActiveObject(group[0], e); 10320 } 10321 else if (group.length > 1) { 10322 group = new fabric.Group(group.reverse(), { 10323 canvas: this 10324 }); 10325 group.addWithUpdate(); 10326 this.setActiveGroup(group, e); 10327 group.saveCoords(); 10328 this.fire('selection:created', { target: group }); 10329 this.renderAll(); 10330 } 10331 }, 10332 10333 /** 10334 * @private 10335 */ 10336 _collectObjects: function() { 10337 var group = [ ], 10338 currentObject, 10339 x1 = this._groupSelector.ex, 10340 y1 = this._groupSelector.ey, 10341 x2 = x1 + this._groupSelector.left, 10342 y2 = y1 + this._groupSelector.top, 10343 selectionX1Y1 = new fabric.Point(min(x1, x2), min(y1, y2)), 10344 selectionX2Y2 = new fabric.Point(max(x1, x2), max(y1, y2)), 10345 isClick = x1 === x2 && y1 === y2; 10346 10347 for (var i = this._objects.length; i--; ) { 10348 currentObject = this._objects[i]; 10349 10350 if (!currentObject || !currentObject.selectable || !currentObject.visible) { 10351 continue; 10352 } 10353 10354 if (currentObject.intersectsWithRect(selectionX1Y1, selectionX2Y2) || 10355 currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2) || 10356 currentObject.containsPoint(selectionX1Y1) || 10357 currentObject.containsPoint(selectionX2Y2) 10358 ) { 10359 currentObject.set('active', true); 10360 group.push(currentObject); 10361 10362 // only add one object if it's a click 10363 if (isClick) { 10364 break; 10365 } 10366 } 10367 } 10368 10369 return group; 10370 }, 10371 10372 /** 10373 * @private 10374 */ 10375 _maybeGroupObjects: function(e) { 10376 if (this.selection && this._groupSelector) { 10377 this._groupSelectedObjects(e); 10378 } 10379 10380 var activeGroup = this.getActiveGroup(); 10381 if (activeGroup) { 10382 activeGroup.setObjectsCoords().setCoords(); 10383 activeGroup.isMoving = false; 10384 this.setCursor(this.defaultCursor); 10385 } 10386 10387 // clear selection and current transformation 10388 this._groupSelector = null; 10389 this._currentTransform = null; 10390 } 10391 }); 10392 10393 })(); 10394 10395 10396 fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { 10397 10398 /** 10399 * Exports canvas element to a dataurl image. Note that when multiplier is used, cropping is scaled appropriately 10400 * @param {Object} [options] Options object 10401 * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" 10402 * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. 10403 * @param {Number} [options.multiplier=1] Multiplier to scale by 10404 * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 10405 * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 10406 * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 10407 * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 10408 * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format 10409 * @see {@link http://jsfiddle.net/fabricjs/NfZVb/|jsFiddle demo} 10410 * @example <caption>Generate jpeg dataURL with lower quality</caption> 10411 * var dataURL = canvas.toDataURL({ 10412 * format: 'jpeg', 10413 * quality: 0.8 10414 * }); 10415 * @example <caption>Generate cropped png dataURL (clipping of canvas)</caption> 10416 * var dataURL = canvas.toDataURL({ 10417 * format: 'png', 10418 * left: 100, 10419 * top: 100, 10420 * width: 200, 10421 * height: 200 10422 * }); 10423 * @example <caption>Generate double scaled png dataURL</caption> 10424 * var dataURL = canvas.toDataURL({ 10425 * format: 'png', 10426 * multiplier: 2 10427 * }); 10428 */ 10429 toDataURL: function (options) { 10430 options || (options = { }); 10431 10432 var format = options.format || 'png', 10433 quality = options.quality || 1, 10434 multiplier = options.multiplier || 1, 10435 cropping = { 10436 left: options.left, 10437 top: options.top, 10438 width: options.width, 10439 height: options.height 10440 }; 10441 10442 if (this._isRetinaScaling()) { 10443 multiplier *= fabric.devicePixelRatio; 10444 } 10445 10446 if (multiplier !== 1) { 10447 return this.__toDataURLWithMultiplier(format, quality, cropping, multiplier); 10448 } 10449 else { 10450 return this.__toDataURL(format, quality, cropping); 10451 } 10452 }, 10453 10454 /** 10455 * @private 10456 */ 10457 __toDataURL: function(format, quality, cropping) { 10458 10459 this.renderAll(); 10460 10461 var canvasEl = this.contextContainer.canvas, 10462 croppedCanvasEl = this.__getCroppedCanvas(canvasEl, cropping); 10463 10464 // to avoid common confusion https://github.com/kangax/fabric.js/issues/806 10465 if (format === 'jpg') { 10466 format = 'jpeg'; 10467 } 10468 10469 var data = (fabric.StaticCanvas.supports('toDataURLWithQuality')) 10470 ? (croppedCanvasEl || canvasEl).toDataURL('image/' + format, quality) 10471 : (croppedCanvasEl || canvasEl).toDataURL('image/' + format); 10472 10473 if (croppedCanvasEl) { 10474 croppedCanvasEl = null; 10475 } 10476 10477 return data; 10478 }, 10479 10480 /** 10481 * @private 10482 */ 10483 __getCroppedCanvas: function(canvasEl, cropping) { 10484 10485 var croppedCanvasEl, 10486 croppedCtx, 10487 shouldCrop = 'left' in cropping || 10488 'top' in cropping || 10489 'width' in cropping || 10490 'height' in cropping; 10491 10492 if (shouldCrop) { 10493 10494 croppedCanvasEl = fabric.util.createCanvasElement(); 10495 croppedCtx = croppedCanvasEl.getContext('2d'); 10496 10497 croppedCanvasEl.width = cropping.width || this.width; 10498 croppedCanvasEl.height = cropping.height || this.height; 10499 10500 croppedCtx.drawImage(canvasEl, -cropping.left || 0, -cropping.top || 0); 10501 } 10502 10503 return croppedCanvasEl; 10504 }, 10505 10506 /** 10507 * @private 10508 */ 10509 __toDataURLWithMultiplier: function(format, quality, cropping, multiplier) { 10510 10511 var origWidth = this.getWidth(), 10512 origHeight = this.getHeight(), 10513 scaledWidth = origWidth * multiplier, 10514 scaledHeight = origHeight * multiplier, 10515 activeObject = this.getActiveObject(), 10516 activeGroup = this.getActiveGroup(), 10517 ctx = this.contextContainer; 10518 10519 if (multiplier > 1) { 10520 this.setDimensions({ width: scaledWidth, height: scaledHeight }); 10521 } 10522 ctx.save(); 10523 ctx.scale(multiplier / fabric.devicePixelRatio, multiplier / fabric.devicePixelRatio); 10524 10525 if (cropping.left) { 10526 cropping.left *= multiplier; 10527 } 10528 if (cropping.top) { 10529 cropping.top *= multiplier; 10530 } 10531 if (cropping.width) { 10532 cropping.width *= multiplier; 10533 } 10534 else if (multiplier < 1) { 10535 cropping.width = scaledWidth; 10536 } 10537 if (cropping.height) { 10538 cropping.height *= multiplier; 10539 } 10540 else if (multiplier < 1) { 10541 cropping.height = scaledHeight; 10542 } 10543 10544 if (activeGroup) { 10545 // not removing group due to complications with restoring it with correct state afterwords 10546 this._tempRemoveBordersControlsFromGroup(activeGroup); 10547 } 10548 else if (activeObject && this.deactivateAll) { 10549 this.deactivateAll(); 10550 } 10551 10552 var data = this.__toDataURL(format, quality, cropping); 10553 10554 // restoring width, height for `renderAll` to draw 10555 // background properly (while context is scaled) 10556 this.width = origWidth; 10557 this.height = origHeight; 10558 this.setDimensions({ width: origWidth, height: origHeight }); 10559 10560 if (activeGroup) { 10561 this._restoreBordersControlsOnGroup(activeGroup); 10562 } 10563 else if (activeObject && this.setActiveObject) { 10564 this.setActiveObject(activeObject); 10565 } 10566 10567 this.contextTop && this.clearContext(this.contextTop); 10568 this.renderAll(); 10569 10570 return data; 10571 }, 10572 10573 /** 10574 * Exports canvas element to a dataurl image (allowing to change image size via multiplier). 10575 * @deprecated since 1.0.13 10576 * @param {String} format (png|jpeg) 10577 * @param {Number} multiplier 10578 * @param {Number} quality (0..1) 10579 * @return {String} 10580 */ 10581 toDataURLWithMultiplier: function (format, multiplier, quality) { 10582 return this.toDataURL({ 10583 format: format, 10584 multiplier: multiplier, 10585 quality: quality 10586 }); 10587 }, 10588 10589 /** 10590 * @private 10591 */ 10592 _tempRemoveBordersControlsFromGroup: function(group) { 10593 group.origHasControls = group.hasControls; 10594 group.origBorderColor = group.borderColor; 10595 10596 group.hasControls = true; 10597 group.borderColor = 'rgba(0,0,0,0)'; 10598 10599 group.forEachObject(function(o) { 10600 o.origBorderColor = o.borderColor; 10601 o.borderColor = 'rgba(0,0,0,0)'; 10602 }); 10603 }, 10604 10605 /** 10606 * @private 10607 */ 10608 _restoreBordersControlsOnGroup: function(group) { 10609 group.hideControls = group.origHideControls; 10610 group.borderColor = group.origBorderColor; 10611 10612 group.forEachObject(function(o) { 10613 o.borderColor = o.origBorderColor; 10614 delete o.origBorderColor; 10615 }); 10616 } 10617 }); 10618 10619 10620 fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { 10621 10622 /** 10623 * Populates canvas with data from the specified dataless JSON. 10624 * JSON format must conform to the one of {@link fabric.Canvas#toDatalessJSON} 10625 * @deprecated since 1.2.2 10626 * @param {String|Object} json JSON string or object 10627 * @param {Function} callback Callback, invoked when json is parsed 10628 * and corresponding objects (e.g: {@link fabric.Image}) 10629 * are initialized 10630 * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created. 10631 * @return {fabric.Canvas} instance 10632 * @chainable 10633 * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#deserialization} 10634 */ 10635 loadFromDatalessJSON: function (json, callback, reviver) { 10636 return this.loadFromJSON(json, callback, reviver); 10637 }, 10638 10639 /** 10640 * Populates canvas with data from the specified JSON. 10641 * JSON format must conform to the one of {@link fabric.Canvas#toJSON} 10642 * @param {String|Object} json JSON string or object 10643 * @param {Function} callback Callback, invoked when json is parsed 10644 * and corresponding objects (e.g: {@link fabric.Image}) 10645 * are initialized 10646 * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created. 10647 * @return {fabric.Canvas} instance 10648 * @chainable 10649 * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#deserialization} 10650 * @see {@link http://jsfiddle.net/fabricjs/fmgXt/|jsFiddle demo} 10651 * @example <caption>loadFromJSON</caption> 10652 * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas)); 10653 * @example <caption>loadFromJSON with reviver</caption> 10654 * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas), function(o, object) { 10655 * // `o` = json object 10656 * // `object` = fabric.Object instance 10657 * // ... do some stuff ... 10658 * }); 10659 */ 10660 loadFromJSON: function (json, callback, reviver) { 10661 if (!json) { 10662 return; 10663 } 10664 10665 // serialize if it wasn't already 10666 var serialized = (typeof json === 'string') 10667 ? JSON.parse(json) 10668 : json; 10669 10670 this.clear(); 10671 10672 var _this = this; 10673 this._enlivenObjects(serialized.objects, function () { 10674 _this._setBgOverlay(serialized, callback); 10675 }, reviver); 10676 10677 return this; 10678 }, 10679 10680 /** 10681 * @private 10682 * @param {Object} serialized Object with background and overlay information 10683 * @param {Function} callback Invoked after all background and overlay images/patterns loaded 10684 */ 10685 _setBgOverlay: function(serialized, callback) { 10686 var _this = this, 10687 loaded = { 10688 backgroundColor: false, 10689 overlayColor: false, 10690 backgroundImage: false, 10691 overlayImage: false 10692 }; 10693 10694 if (!serialized.backgroundImage && !serialized.overlayImage && !serialized.background && !serialized.overlay) { 10695 callback && callback(); 10696 return; 10697 } 10698 10699 var cbIfLoaded = function () { 10700 if (loaded.backgroundImage && loaded.overlayImage && loaded.backgroundColor && loaded.overlayColor) { 10701 _this.renderAll(); 10702 callback && callback(); 10703 } 10704 }; 10705 10706 this.__setBgOverlay('backgroundImage', serialized.backgroundImage, loaded, cbIfLoaded); 10707 this.__setBgOverlay('overlayImage', serialized.overlayImage, loaded, cbIfLoaded); 10708 this.__setBgOverlay('backgroundColor', serialized.background, loaded, cbIfLoaded); 10709 this.__setBgOverlay('overlayColor', serialized.overlay, loaded, cbIfLoaded); 10710 10711 cbIfLoaded(); 10712 }, 10713 10714 /** 10715 * @private 10716 * @param {String} property Property to set (backgroundImage, overlayImage, backgroundColor, overlayColor) 10717 * @param {(Object|String)} value Value to set 10718 * @param {Object} loaded Set loaded property to true if property is set 10719 * @param {Object} callback Callback function to invoke after property is set 10720 */ 10721 __setBgOverlay: function(property, value, loaded, callback) { 10722 var _this = this; 10723 10724 if (!value) { 10725 loaded[property] = true; 10726 return; 10727 } 10728 10729 if (property === 'backgroundImage' || property === 'overlayImage') { 10730 fabric.Image.fromObject(value, function(img) { 10731 _this[property] = img; 10732 loaded[property] = true; 10733 callback && callback(); 10734 }); 10735 } 10736 else { 10737 this['set' + fabric.util.string.capitalize(property, true)](value, function() { 10738 loaded[property] = true; 10739 callback && callback(); 10740 }); 10741 } 10742 }, 10743 10744 /** 10745 * @private 10746 * @param {Array} objects 10747 * @param {Function} callback 10748 * @param {Function} [reviver] 10749 */ 10750 _enlivenObjects: function (objects, callback, reviver) { 10751 var _this = this; 10752 10753 if (!objects || objects.length === 0) { 10754 callback && callback(); 10755 return; 10756 } 10757 10758 var renderOnAddRemove = this.renderOnAddRemove; 10759 this.renderOnAddRemove = false; 10760 10761 fabric.util.enlivenObjects(objects, function(enlivenedObjects) { 10762 enlivenedObjects.forEach(function(obj, index) { 10763 _this.insertAt(obj, index, true); 10764 }); 10765 10766 _this.renderOnAddRemove = renderOnAddRemove; 10767 callback && callback(); 10768 }, null, reviver); 10769 }, 10770 10771 /** 10772 * @private 10773 * @param {String} format 10774 * @param {Function} callback 10775 */ 10776 _toDataURL: function (format, callback) { 10777 this.clone(function (clone) { 10778 callback(clone.toDataURL(format)); 10779 }); 10780 }, 10781 10782 /** 10783 * @private 10784 * @param {String} format 10785 * @param {Number} multiplier 10786 * @param {Function} callback 10787 */ 10788 _toDataURLWithMultiplier: function (format, multiplier, callback) { 10789 this.clone(function (clone) { 10790 callback(clone.toDataURLWithMultiplier(format, multiplier)); 10791 }); 10792 }, 10793 10794 /** 10795 * Clones canvas instance 10796 * @param {Object} [callback] Receives cloned instance as a first argument 10797 * @param {Array} [properties] Array of properties to include in the cloned canvas and children 10798 */ 10799 clone: function (callback, properties) { 10800 var data = JSON.stringify(this.toJSON(properties)); 10801 this.cloneWithoutData(function(clone) { 10802 clone.loadFromJSON(data, function() { 10803 callback && callback(clone); 10804 }); 10805 }); 10806 }, 10807 10808 /** 10809 * Clones canvas instance without cloning existing data. 10810 * This essentially copies canvas dimensions, clipping properties, etc. 10811 * but leaves data empty (so that you can populate it with your own) 10812 * @param {Object} [callback] Receives cloned instance as a first argument 10813 */ 10814 cloneWithoutData: function(callback) { 10815 var el = fabric.document.createElement('canvas'); 10816 10817 el.width = this.getWidth(); 10818 el.height = this.getHeight(); 10819 10820 var clone = new fabric.Canvas(el); 10821 clone.clipTo = this.clipTo; 10822 if (this.backgroundImage) { 10823 clone.setBackgroundImage(this.backgroundImage.src, function() { 10824 clone.renderAll(); 10825 callback && callback(clone); 10826 }); 10827 clone.backgroundImageOpacity = this.backgroundImageOpacity; 10828 clone.backgroundImageStretch = this.backgroundImageStretch; 10829 } 10830 else { 10831 callback && callback(clone); 10832 } 10833 } 10834 }); 10835 10836 10837 (function(global) { 10838 10839 'use strict'; 10840 10841 var fabric = global.fabric || (global.fabric = { }), 10842 extend = fabric.util.object.extend, 10843 toFixed = fabric.util.toFixed, 10844 capitalize = fabric.util.string.capitalize, 10845 degreesToRadians = fabric.util.degreesToRadians, 10846 supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); 10847 10848 if (fabric.Object) { 10849 return; 10850 } 10851 10852 /** 10853 * Root object class from which all 2d shape classes inherit from 10854 * @class fabric.Object 10855 * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#objects} 10856 * @see {@link fabric.Object#initialize} for constructor definition 10857 * 10858 * @fires added 10859 * @fires removed 10860 * 10861 * @fires selected 10862 * @fires modified 10863 * @fires rotating 10864 * @fires scaling 10865 * @fires moving 10866 * @fires skewing 10867 * 10868 * @fires mousedown 10869 * @fires mouseup 10870 */ 10871 fabric.Object = fabric.util.createClass(/** @lends fabric.Object.prototype */ { 10872 10873 /** 10874 * Retrieves object's {@link fabric.Object#clipTo|clipping function} 10875 * @method getClipTo 10876 * @memberOf fabric.Object.prototype 10877 * @return {Function} 10878 */ 10879 10880 /** 10881 * Sets object's {@link fabric.Object#clipTo|clipping function} 10882 * @method setClipTo 10883 * @memberOf fabric.Object.prototype 10884 * @param {Function} clipTo Clipping function 10885 * @return {fabric.Object} thisArg 10886 * @chainable 10887 */ 10888 10889 /** 10890 * Retrieves object's {@link fabric.Object#transformMatrix|transformMatrix} 10891 * @method getTransformMatrix 10892 * @memberOf fabric.Object.prototype 10893 * @return {Array} transformMatrix 10894 */ 10895 10896 /** 10897 * Sets object's {@link fabric.Object#transformMatrix|transformMatrix} 10898 * @method setTransformMatrix 10899 * @memberOf fabric.Object.prototype 10900 * @param {Array} transformMatrix 10901 * @return {fabric.Object} thisArg 10902 * @chainable 10903 */ 10904 10905 /** 10906 * Retrieves object's {@link fabric.Object#visible|visible} state 10907 * @method getVisible 10908 * @memberOf fabric.Object.prototype 10909 * @return {Boolean} True if visible 10910 */ 10911 10912 /** 10913 * Sets object's {@link fabric.Object#visible|visible} state 10914 * @method setVisible 10915 * @memberOf fabric.Object.prototype 10916 * @param {Boolean} value visible value 10917 * @return {fabric.Object} thisArg 10918 * @chainable 10919 */ 10920 10921 /** 10922 * Retrieves object's {@link fabric.Object#shadow|shadow} 10923 * @method getShadow 10924 * @memberOf fabric.Object.prototype 10925 * @return {Object} Shadow instance 10926 */ 10927 10928 /** 10929 * Retrieves object's {@link fabric.Object#stroke|stroke} 10930 * @method getStroke 10931 * @memberOf fabric.Object.prototype 10932 * @return {String} stroke value 10933 */ 10934 10935 /** 10936 * Sets object's {@link fabric.Object#stroke|stroke} 10937 * @method setStroke 10938 * @memberOf fabric.Object.prototype 10939 * @param {String} value stroke value 10940 * @return {fabric.Object} thisArg 10941 * @chainable 10942 */ 10943 10944 /** 10945 * Retrieves object's {@link fabric.Object#strokeWidth|strokeWidth} 10946 * @method getStrokeWidth 10947 * @memberOf fabric.Object.prototype 10948 * @return {Number} strokeWidth value 10949 */ 10950 10951 /** 10952 * Sets object's {@link fabric.Object#strokeWidth|strokeWidth} 10953 * @method setStrokeWidth 10954 * @memberOf fabric.Object.prototype 10955 * @param {Number} value strokeWidth value 10956 * @return {fabric.Object} thisArg 10957 * @chainable 10958 */ 10959 10960 /** 10961 * Retrieves object's {@link fabric.Object#originX|originX} 10962 * @method getOriginX 10963 * @memberOf fabric.Object.prototype 10964 * @return {String} originX value 10965 */ 10966 10967 /** 10968 * Sets object's {@link fabric.Object#originX|originX} 10969 * @method setOriginX 10970 * @memberOf fabric.Object.prototype 10971 * @param {String} value originX value 10972 * @return {fabric.Object} thisArg 10973 * @chainable 10974 */ 10975 10976 /** 10977 * Retrieves object's {@link fabric.Object#originY|originY} 10978 * @method getOriginY 10979 * @memberOf fabric.Object.prototype 10980 * @return {String} originY value 10981 */ 10982 10983 /** 10984 * Sets object's {@link fabric.Object#originY|originY} 10985 * @method setOriginY 10986 * @memberOf fabric.Object.prototype 10987 * @param {String} value originY value 10988 * @return {fabric.Object} thisArg 10989 * @chainable 10990 */ 10991 10992 /** 10993 * Retrieves object's {@link fabric.Object#fill|fill} 10994 * @method getFill 10995 * @memberOf fabric.Object.prototype 10996 * @return {String} Fill value 10997 */ 10998 10999 /** 11000 * Sets object's {@link fabric.Object#fill|fill} 11001 * @method setFill 11002 * @memberOf fabric.Object.prototype 11003 * @param {String} value Fill value 11004 * @return {fabric.Object} thisArg 11005 * @chainable 11006 */ 11007 11008 /** 11009 * Retrieves object's {@link fabric.Object#opacity|opacity} 11010 * @method getOpacity 11011 * @memberOf fabric.Object.prototype 11012 * @return {Number} Opacity value (0-1) 11013 */ 11014 11015 /** 11016 * Sets object's {@link fabric.Object#opacity|opacity} 11017 * @method setOpacity 11018 * @memberOf fabric.Object.prototype 11019 * @param {Number} value Opacity value (0-1) 11020 * @return {fabric.Object} thisArg 11021 * @chainable 11022 */ 11023 11024 /** 11025 * Retrieves object's {@link fabric.Object#angle|angle} (in degrees) 11026 * @method getAngle 11027 * @memberOf fabric.Object.prototype 11028 * @return {Number} 11029 */ 11030 11031 /** 11032 * Retrieves object's {@link fabric.Object#top|top position} 11033 * @method getTop 11034 * @memberOf fabric.Object.prototype 11035 * @return {Number} Top value (in pixels) 11036 */ 11037 11038 /** 11039 * Sets object's {@link fabric.Object#top|top position} 11040 * @method setTop 11041 * @memberOf fabric.Object.prototype 11042 * @param {Number} value Top value (in pixels) 11043 * @return {fabric.Object} thisArg 11044 * @chainable 11045 */ 11046 11047 /** 11048 * Retrieves object's {@link fabric.Object#left|left position} 11049 * @method getLeft 11050 * @memberOf fabric.Object.prototype 11051 * @return {Number} Left value (in pixels) 11052 */ 11053 11054 /** 11055 * Sets object's {@link fabric.Object#left|left position} 11056 * @method setLeft 11057 * @memberOf fabric.Object.prototype 11058 * @param {Number} value Left value (in pixels) 11059 * @return {fabric.Object} thisArg 11060 * @chainable 11061 */ 11062 11063 /** 11064 * Retrieves object's {@link fabric.Object#scaleX|scaleX} value 11065 * @method getScaleX 11066 * @memberOf fabric.Object.prototype 11067 * @return {Number} scaleX value 11068 */ 11069 11070 /** 11071 * Sets object's {@link fabric.Object#scaleX|scaleX} value 11072 * @method setScaleX 11073 * @memberOf fabric.Object.prototype 11074 * @param {Number} value scaleX value 11075 * @return {fabric.Object} thisArg 11076 * @chainable 11077 */ 11078 11079 /** 11080 * Retrieves object's {@link fabric.Object#scaleY|scaleY} value 11081 * @method getScaleY 11082 * @memberOf fabric.Object.prototype 11083 * @return {Number} scaleY value 11084 */ 11085 11086 /** 11087 * Sets object's {@link fabric.Object#scaleY|scaleY} value 11088 * @method setScaleY 11089 * @memberOf fabric.Object.prototype 11090 * @param {Number} value scaleY value 11091 * @return {fabric.Object} thisArg 11092 * @chainable 11093 */ 11094 11095 /** 11096 * Retrieves object's {@link fabric.Object#flipX|flipX} value 11097 * @method getFlipX 11098 * @memberOf fabric.Object.prototype 11099 * @return {Boolean} flipX value 11100 */ 11101 11102 /** 11103 * Sets object's {@link fabric.Object#flipX|flipX} value 11104 * @method setFlipX 11105 * @memberOf fabric.Object.prototype 11106 * @param {Boolean} value flipX value 11107 * @return {fabric.Object} thisArg 11108 * @chainable 11109 */ 11110 11111 /** 11112 * Retrieves object's {@link fabric.Object#flipY|flipY} value 11113 * @method getFlipY 11114 * @memberOf fabric.Object.prototype 11115 * @return {Boolean} flipY value 11116 */ 11117 11118 /** 11119 * Sets object's {@link fabric.Object#flipY|flipY} value 11120 * @method setFlipY 11121 * @memberOf fabric.Object.prototype 11122 * @param {Boolean} value flipY value 11123 * @return {fabric.Object} thisArg 11124 * @chainable 11125 */ 11126 11127 /** 11128 * Type of an object (rect, circle, path, etc.). 11129 * Note that this property is meant to be read-only and not meant to be modified. 11130 * If you modify, certain parts of Fabric (such as JSON loading) won't work correctly. 11131 * @type String 11132 * @default 11133 */ 11134 type: 'object', 11135 11136 /** 11137 * Horizontal origin of transformation of an object (one of "left", "right", "center") 11138 * See http://jsfiddle.net/1ow02gea/40/ on how originX/originY affect objects in groups 11139 * @type String 11140 * @default 11141 */ 11142 originX: 'left', 11143 11144 /** 11145 * Vertical origin of transformation of an object (one of "top", "bottom", "center") 11146 * See http://jsfiddle.net/1ow02gea/40/ on how originX/originY affect objects in groups 11147 * @type String 11148 * @default 11149 */ 11150 originY: 'top', 11151 11152 /** 11153 * Top position of an object. Note that by default it's relative to object top. You can change this by setting originY={top/center/bottom} 11154 * @type Number 11155 * @default 11156 */ 11157 top: 0, 11158 11159 /** 11160 * Left position of an object. Note that by default it's relative to object left. You can change this by setting originX={left/center/right} 11161 * @type Number 11162 * @default 11163 */ 11164 left: 0, 11165 11166 /** 11167 * Object width 11168 * @type Number 11169 * @default 11170 */ 11171 width: 0, 11172 11173 /** 11174 * Object height 11175 * @type Number 11176 * @default 11177 */ 11178 height: 0, 11179 11180 /** 11181 * Object scale factor (horizontal) 11182 * @type Number 11183 * @default 11184 */ 11185 scaleX: 1, 11186 11187 /** 11188 * Object scale factor (vertical) 11189 * @type Number 11190 * @default 11191 */ 11192 scaleY: 1, 11193 11194 /** 11195 * When true, an object is rendered as flipped horizontally 11196 * @type Boolean 11197 * @default 11198 */ 11199 flipX: false, 11200 11201 /** 11202 * When true, an object is rendered as flipped vertically 11203 * @type Boolean 11204 * @default 11205 */ 11206 flipY: false, 11207 11208 /** 11209 * Opacity of an object 11210 * @type Number 11211 * @default 11212 */ 11213 opacity: 1, 11214 11215 /** 11216 * Angle of rotation of an object (in degrees) 11217 * @type Number 11218 * @default 11219 */ 11220 angle: 0, 11221 11222 /** 11223 * Angle of skew on x axes of an object (in degrees) 11224 * @type Number 11225 * @default 11226 */ 11227 skewX: 0, 11228 11229 /** 11230 * Angle of skew on y axes of an object (in degrees) 11231 * @type Number 11232 * @default 11233 */ 11234 skewY: 0, 11235 11236 /** 11237 * Size of object's controlling corners (in pixels) 11238 * @type Number 11239 * @default 11240 */ 11241 cornerSize: 12, 11242 11243 /** 11244 * When true, object's controlling corners are rendered as transparent inside (i.e. stroke instead of fill) 11245 * @type Boolean 11246 * @default 11247 */ 11248 transparentCorners: true, 11249 11250 /** 11251 * Default cursor value used when hovering over this object on canvas 11252 * @type String 11253 * @default 11254 */ 11255 hoverCursor: null, 11256 11257 /** 11258 * Padding between object and its controlling borders (in pixels) 11259 * @type Number 11260 * @default 11261 */ 11262 padding: 0, 11263 11264 /** 11265 * Color of controlling borders of an object (when it's active) 11266 * @type String 11267 * @default 11268 */ 11269 borderColor: 'rgba(102,153,255,0.75)', 11270 11271 /** 11272 * Color of controlling corners of an object (when it's active) 11273 * @type String 11274 * @default 11275 */ 11276 cornerColor: 'rgba(102,153,255,0.5)', 11277 11278 /** 11279 * When true, this object will use center point as the origin of transformation 11280 * when being scaled via the controls. 11281 * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean). 11282 * @since 1.3.4 11283 * @type Boolean 11284 * @default 11285 */ 11286 centeredScaling: false, 11287 11288 /** 11289 * When true, this object will use center point as the origin of transformation 11290 * when being rotated via the controls. 11291 * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean). 11292 * @since 1.3.4 11293 * @type Boolean 11294 * @default 11295 */ 11296 centeredRotation: true, 11297 11298 /** 11299 * Color of object's fill 11300 * @type String 11301 * @default 11302 */ 11303 fill: 'rgb(0,0,0)', 11304 11305 /** 11306 * Fill rule used to fill an object 11307 * accepted values are nonzero, evenodd 11308 * <b>Backwards incompatibility note:</b> This property was used for setting globalCompositeOperation until v1.4.12 (use `fabric.Object#globalCompositeOperation` instead) 11309 * @type String 11310 * @default 11311 */ 11312 fillRule: 'nonzero', 11313 11314 /** 11315 * Composite rule used for canvas globalCompositeOperation 11316 * @type String 11317 * @default 11318 */ 11319 globalCompositeOperation: 'source-over', 11320 11321 /** 11322 * Background color of an object. Only works with text objects at the moment. 11323 * @type String 11324 * @default 11325 */ 11326 backgroundColor: '', 11327 11328 /** 11329 * When defined, an object is rendered via stroke and this property specifies its color 11330 * @type String 11331 * @default 11332 */ 11333 stroke: null, 11334 11335 /** 11336 * Width of a stroke used to render this object 11337 * @type Number 11338 * @default 11339 */ 11340 strokeWidth: 1, 11341 11342 /** 11343 * Array specifying dash pattern of an object's stroke (stroke must be defined) 11344 * @type Array 11345 */ 11346 strokeDashArray: null, 11347 11348 /** 11349 * Line endings style of an object's stroke (one of "butt", "round", "square") 11350 * @type String 11351 * @default 11352 */ 11353 strokeLineCap: 'butt', 11354 11355 /** 11356 * Corner style of an object's stroke (one of "bevil", "round", "miter") 11357 * @type String 11358 * @default 11359 */ 11360 strokeLineJoin: 'miter', 11361 11362 /** 11363 * Maximum miter length (used for strokeLineJoin = "miter") of an object's stroke 11364 * @type Number 11365 * @default 11366 */ 11367 strokeMiterLimit: 10, 11368 11369 /** 11370 * Shadow object representing shadow of this shape 11371 * @type fabric.Shadow 11372 * @default 11373 */ 11374 shadow: null, 11375 11376 /** 11377 * Opacity of object's controlling borders when object is active and moving 11378 * @type Number 11379 * @default 11380 */ 11381 borderOpacityWhenMoving: 0.4, 11382 11383 /** 11384 * Scale factor of object's controlling borders 11385 * @type Number 11386 * @default 11387 */ 11388 borderScaleFactor: 1, 11389 11390 /** 11391 * Transform matrix (similar to SVG's transform matrix) 11392 * @type Array 11393 */ 11394 transformMatrix: null, 11395 11396 /** 11397 * Minimum allowed scale value of an object 11398 * @type Number 11399 * @default 11400 */ 11401 minScaleLimit: 0.01, 11402 11403 /** 11404 * When set to `false`, an object can not be selected for modification (using either point-click-based or group-based selection). 11405 * But events still fire on it. 11406 * @type Boolean 11407 * @default 11408 */ 11409 selectable: true, 11410 11411 /** 11412 * When set to `false`, an object can not be a target of events. All events propagate through it. Introduced in v1.3.4 11413 * @type Boolean 11414 * @default 11415 */ 11416 evented: true, 11417 11418 /** 11419 * When set to `false`, an object is not rendered on canvas 11420 * @type Boolean 11421 * @default 11422 */ 11423 visible: true, 11424 11425 /** 11426 * When set to `false`, object's controls are not displayed and can not be used to manipulate object 11427 * @type Boolean 11428 * @default 11429 */ 11430 hasControls: true, 11431 11432 /** 11433 * When set to `false`, object's controlling borders are not rendered 11434 * @type Boolean 11435 * @default 11436 */ 11437 hasBorders: true, 11438 11439 /** 11440 * When set to `false`, object's controlling rotating point will not be visible or selectable 11441 * @type Boolean 11442 * @default 11443 */ 11444 hasRotatingPoint: true, 11445 11446 /** 11447 * Offset for object's controlling rotating point (when enabled via `hasRotatingPoint`) 11448 * @type Number 11449 * @default 11450 */ 11451 rotatingPointOffset: 40, 11452 11453 /** 11454 * When set to `true`, objects are "found" on canvas on per-pixel basis rather than according to bounding box 11455 * @type Boolean 11456 * @default 11457 */ 11458 perPixelTargetFind: false, 11459 11460 /** 11461 * When `false`, default object's values are not included in its serialization 11462 * @type Boolean 11463 * @default 11464 */ 11465 includeDefaultValues: true, 11466 11467 /** 11468 * Function that determines clipping of an object (context is passed as a first argument) 11469 * Note that context origin is at the object's center point (not left/top corner) 11470 * @type Function 11471 */ 11472 clipTo: null, 11473 11474 /** 11475 * When `true`, object horizontal movement is locked 11476 * @type Boolean 11477 * @default 11478 */ 11479 lockMovementX: false, 11480 11481 /** 11482 * When `true`, object vertical movement is locked 11483 * @type Boolean 11484 * @default 11485 */ 11486 lockMovementY: false, 11487 11488 /** 11489 * When `true`, object rotation is locked 11490 * @type Boolean 11491 * @default 11492 */ 11493 lockRotation: false, 11494 11495 /** 11496 * When `true`, object horizontal scaling is locked 11497 * @type Boolean 11498 * @default 11499 */ 11500 lockScalingX: false, 11501 11502 /** 11503 * When `true`, object vertical scaling is locked 11504 * @type Boolean 11505 * @default 11506 */ 11507 lockScalingY: false, 11508 11509 /** 11510 * When `true`, object non-uniform scaling is locked 11511 * @type Boolean 11512 * @default 11513 */ 11514 lockUniScaling: false, 11515 11516 /** 11517 * When `true`, object horizontal skewing is locked 11518 * @type Boolean 11519 * @default 11520 */ 11521 lockSkewingX: false, 11522 11523 /** 11524 * When `true`, object vertical skewing is locked 11525 * @type Boolean 11526 * @default 11527 */ 11528 lockSkewingY: false, 11529 11530 /** 11531 * When `true`, object cannot be flipped by scaling into negative values 11532 * @type Boolean 11533 * @default 11534 */ 11535 11536 lockScalingFlip: false, 11537 /** 11538 * List of properties to consider when checking if state 11539 * of an object is changed (fabric.Object#hasStateChanged) 11540 * as well as for history (undo/redo) purposes 11541 * @type Array 11542 */ 11543 stateProperties: ( 11544 'top left width height scaleX scaleY flipX flipY originX originY transformMatrix ' + 11545 'stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit ' + 11546 'angle opacity fill fillRule globalCompositeOperation shadow clipTo visible backgroundColor ' + 11547 'alignX alignY meetOrSlice skewX skewY' 11548 ).split(' '), 11549 11550 /** 11551 * Constructor 11552 * @param {Object} [options] Options object 11553 */ 11554 initialize: function(options) { 11555 if (options) { 11556 this.setOptions(options); 11557 } 11558 }, 11559 11560 /** 11561 * @private 11562 * @param {Object} [options] Options object 11563 */ 11564 _initGradient: function(options) { 11565 if (options.fill && options.fill.colorStops && !(options.fill instanceof fabric.Gradient)) { 11566 this.set('fill', new fabric.Gradient(options.fill)); 11567 } 11568 if (options.stroke && options.stroke.colorStops && !(options.stroke instanceof fabric.Gradient)) { 11569 this.set('stroke', new fabric.Gradient(options.stroke)); 11570 } 11571 }, 11572 11573 /** 11574 * @private 11575 * @param {Object} [options] Options object 11576 */ 11577 _initPattern: function(options) { 11578 if (options.fill && options.fill.source && !(options.fill instanceof fabric.Pattern)) { 11579 this.set('fill', new fabric.Pattern(options.fill)); 11580 } 11581 if (options.stroke && options.stroke.source && !(options.stroke instanceof fabric.Pattern)) { 11582 this.set('stroke', new fabric.Pattern(options.stroke)); 11583 } 11584 }, 11585 11586 /** 11587 * @private 11588 * @param {Object} [options] Options object 11589 */ 11590 _initClipping: function(options) { 11591 if (!options.clipTo || typeof options.clipTo !== 'string') { 11592 return; 11593 } 11594 11595 var functionBody = fabric.util.getFunctionBody(options.clipTo); 11596 if (typeof functionBody !== 'undefined') { 11597 this.clipTo = new Function('ctx', functionBody); 11598 } 11599 }, 11600 11601 /** 11602 * Sets object's properties from options 11603 * @param {Object} [options] Options object 11604 */ 11605 setOptions: function(options) { 11606 for (var prop in options) { 11607 this.set(prop, options[prop]); 11608 } 11609 this._initGradient(options); 11610 this._initPattern(options); 11611 this._initClipping(options); 11612 }, 11613 11614 /** 11615 * Transforms context when rendering an object 11616 * @param {CanvasRenderingContext2D} ctx Context 11617 * @param {Boolean} fromLeft When true, context is transformed to object's top/left corner. This is used when rendering text on Node 11618 */ 11619 transform: function(ctx, fromLeft) { 11620 if (this.group && this.canvas.preserveObjectStacking && this.group === this.canvas._activeGroup) { 11621 this.group.transform(ctx); 11622 } 11623 var center = fromLeft ? this._getLeftTopCoords() : this.getCenterPoint(); 11624 ctx.translate(center.x, center.y); 11625 ctx.rotate(degreesToRadians(this.angle)); 11626 ctx.scale( 11627 this.scaleX * (this.flipX ? -1 : 1), 11628 this.scaleY * (this.flipY ? -1 : 1) 11629 ); 11630 ctx.transform(1, 0, Math.tan(degreesToRadians(this.skewX)), 1, 0, 0); 11631 ctx.transform(1, Math.tan(degreesToRadians(this.skewY)), 0, 1, 0, 0); 11632 }, 11633 11634 /** 11635 * Returns an object representation of an instance 11636 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 11637 * @return {Object} Object representation of an instance 11638 */ 11639 toObject: function(propertiesToInclude) { 11640 var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, 11641 11642 object = { 11643 type: this.type, 11644 originX: this.originX, 11645 originY: this.originY, 11646 left: toFixed(this.left, NUM_FRACTION_DIGITS), 11647 top: toFixed(this.top, NUM_FRACTION_DIGITS), 11648 width: toFixed(this.width, NUM_FRACTION_DIGITS), 11649 height: toFixed(this.height, NUM_FRACTION_DIGITS), 11650 fill: (this.fill && this.fill.toObject) ? this.fill.toObject() : this.fill, 11651 stroke: (this.stroke && this.stroke.toObject) ? this.stroke.toObject() : this.stroke, 11652 strokeWidth: toFixed(this.strokeWidth, NUM_FRACTION_DIGITS), 11653 strokeDashArray: this.strokeDashArray ? this.strokeDashArray.concat() : this.strokeDashArray, 11654 strokeLineCap: this.strokeLineCap, 11655 strokeLineJoin: this.strokeLineJoin, 11656 strokeMiterLimit: toFixed(this.strokeMiterLimit, NUM_FRACTION_DIGITS), 11657 scaleX: toFixed(this.scaleX, NUM_FRACTION_DIGITS), 11658 scaleY: toFixed(this.scaleY, NUM_FRACTION_DIGITS), 11659 angle: toFixed(this.getAngle(), NUM_FRACTION_DIGITS), 11660 flipX: this.flipX, 11661 flipY: this.flipY, 11662 opacity: toFixed(this.opacity, NUM_FRACTION_DIGITS), 11663 shadow: (this.shadow && this.shadow.toObject) ? this.shadow.toObject() : this.shadow, 11664 visible: this.visible, 11665 clipTo: this.clipTo && String(this.clipTo), 11666 backgroundColor: this.backgroundColor, 11667 fillRule: this.fillRule, 11668 globalCompositeOperation: this.globalCompositeOperation, 11669 transformMatrix: this.transformMatrix ? this.transformMatrix.concat() : this.transformMatrix, 11670 skewX: toFixed(this.skewX, NUM_FRACTION_DIGITS), 11671 skewY: toFixed(this.skewY, NUM_FRACTION_DIGITS) 11672 }; 11673 11674 if (!this.includeDefaultValues) { 11675 object = this._removeDefaultValues(object); 11676 } 11677 11678 fabric.util.populateWithProperties(this, object, propertiesToInclude); 11679 11680 return object; 11681 }, 11682 11683 /** 11684 * Returns (dataless) object representation of an instance 11685 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 11686 * @return {Object} Object representation of an instance 11687 */ 11688 toDatalessObject: function(propertiesToInclude) { 11689 // will be overwritten by subclasses 11690 return this.toObject(propertiesToInclude); 11691 }, 11692 11693 /** 11694 * @private 11695 * @param {Object} object 11696 */ 11697 _removeDefaultValues: function(object) { 11698 var prototype = fabric.util.getKlass(object.type).prototype, 11699 stateProperties = prototype.stateProperties; 11700 11701 stateProperties.forEach(function(prop) { 11702 if (object[prop] === prototype[prop]) { 11703 delete object[prop]; 11704 } 11705 var isArray = Object.prototype.toString.call(object[prop]) === '[object Array]' && 11706 Object.prototype.toString.call(prototype[prop]) === '[object Array]'; 11707 11708 // basically a check for [] === [] 11709 if (isArray && object[prop].length === 0 && prototype[prop].length === 0) { 11710 delete object[prop]; 11711 } 11712 }); 11713 11714 return object; 11715 }, 11716 11717 /** 11718 * Returns a string representation of an instance 11719 * @return {String} 11720 */ 11721 toString: function() { 11722 return '#<fabric.' + capitalize(this.type) + '>'; 11723 }, 11724 11725 /** 11726 * Basic getter 11727 * @param {String} property Property name 11728 * @return {Any} value of a property 11729 */ 11730 get: function(property) { 11731 return this[property]; 11732 }, 11733 11734 /** 11735 * @private 11736 */ 11737 _setObject: function(obj) { 11738 for (var prop in obj) { 11739 this._set(prop, obj[prop]); 11740 } 11741 }, 11742 11743 /** 11744 * Sets property to a given value. When changing position/dimension -related properties (left, top, scale, angle, etc.) `set` does not update position of object's borders/controls. If you need to update those, call `setCoords()`. 11745 * @param {String|Object} key Property name or object (if object, iterate over the object properties) 11746 * @param {Object|Function} value Property value (if function, the value is passed into it and its return value is used as a new one) 11747 * @return {fabric.Object} thisArg 11748 * @chainable 11749 */ 11750 set: function(key, value) { 11751 if (typeof key === 'object') { 11752 this._setObject(key); 11753 } 11754 else { 11755 if (typeof value === 'function' && key !== 'clipTo') { 11756 this._set(key, value(this.get(key))); 11757 } 11758 else { 11759 this._set(key, value); 11760 } 11761 } 11762 return this; 11763 }, 11764 11765 /** 11766 * @private 11767 * @param {String} key 11768 * @param {Any} value 11769 * @return {fabric.Object} thisArg 11770 */ 11771 _set: function(key, value) { 11772 var shouldConstrainValue = (key === 'scaleX' || key === 'scaleY'); 11773 11774 if (shouldConstrainValue) { 11775 value = this._constrainScale(value); 11776 } 11777 if (key === 'scaleX' && value < 0) { 11778 this.flipX = !this.flipX; 11779 value *= -1; 11780 } 11781 else if (key === 'scaleY' && value < 0) { 11782 this.flipY = !this.flipY; 11783 value *= -1; 11784 } 11785 else if (key === 'width' || key === 'height') { 11786 this.minScaleLimit = toFixed(Math.min(0.1, 1/Math.max(this.width, this.height)), 2); 11787 } 11788 else if (key === 'shadow' && value && !(value instanceof fabric.Shadow)) { 11789 value = new fabric.Shadow(value); 11790 } 11791 11792 this[key] = value; 11793 11794 return this; 11795 }, 11796 11797 /** 11798 * This callback function is called by the parent group of an object every 11799 * time a non-delegated property changes on the group. It is passed the key 11800 * and value as parameters. Not adding in this function's signature to avoid 11801 * Travis build error about unused variables. 11802 */ 11803 setOnGroup: function() { 11804 // implemented by sub-classes, as needed. 11805 }, 11806 11807 /** 11808 * Toggles specified property from `true` to `false` or from `false` to `true` 11809 * @param {String} property Property to toggle 11810 * @return {fabric.Object} thisArg 11811 * @chainable 11812 */ 11813 toggle: function(property) { 11814 var value = this.get(property); 11815 if (typeof value === 'boolean') { 11816 this.set(property, !value); 11817 } 11818 return this; 11819 }, 11820 11821 /** 11822 * Sets sourcePath of an object 11823 * @param {String} value Value to set sourcePath to 11824 * @return {fabric.Object} thisArg 11825 * @chainable 11826 */ 11827 setSourcePath: function(value) { 11828 this.sourcePath = value; 11829 return this; 11830 }, 11831 11832 /** 11833 * Retrieves viewportTransform from Object's canvas if possible 11834 * @method getViewportTransform 11835 * @memberOf fabric.Object.prototype 11836 * @return {Boolean} flipY value // TODO 11837 */ 11838 getViewportTransform: function() { 11839 if (this.canvas && this.canvas.viewportTransform) { 11840 return this.canvas.viewportTransform; 11841 } 11842 return [1, 0, 0, 1, 0, 0]; 11843 }, 11844 11845 /** 11846 * Renders an object on a specified context 11847 * @param {CanvasRenderingContext2D} ctx Context to render on 11848 * @param {Boolean} [noTransform] When true, context is not transformed 11849 */ 11850 render: function(ctx, noTransform) { 11851 // do not render if width/height are zeros or object is not visible 11852 if ((this.width === 0 && this.height === 0) || !this.visible) { 11853 return; 11854 } 11855 11856 ctx.save(); 11857 11858 //setup fill rule for current object 11859 this._setupCompositeOperation(ctx); 11860 if (!noTransform) { 11861 this.transform(ctx); 11862 } 11863 this._setStrokeStyles(ctx); 11864 this._setFillStyles(ctx); 11865 if (this.transformMatrix) { 11866 ctx.transform.apply(ctx, this.transformMatrix); 11867 } 11868 this._setOpacity(ctx); 11869 this._setShadow(ctx); 11870 this.clipTo && fabric.util.clipContext(this, ctx); 11871 this._render(ctx, noTransform); 11872 this.clipTo && ctx.restore(); 11873 11874 ctx.restore(); 11875 }, 11876 11877 /** 11878 * @private 11879 * @param {CanvasRenderingContext2D} ctx Context to render on 11880 */ 11881 _setOpacity: function(ctx) { 11882 if (this.group) { 11883 this.group._setOpacity(ctx); 11884 } 11885 ctx.globalAlpha *= this.opacity; 11886 }, 11887 11888 _setStrokeStyles: function(ctx) { 11889 if (this.stroke) { 11890 ctx.lineWidth = this.strokeWidth; 11891 ctx.lineCap = this.strokeLineCap; 11892 ctx.lineJoin = this.strokeLineJoin; 11893 ctx.miterLimit = this.strokeMiterLimit; 11894 ctx.strokeStyle = this.stroke.toLive 11895 ? this.stroke.toLive(ctx, this) 11896 : this.stroke; 11897 } 11898 }, 11899 11900 _setFillStyles: function(ctx) { 11901 if (this.fill) { 11902 ctx.fillStyle = this.fill.toLive 11903 ? this.fill.toLive(ctx, this) 11904 : this.fill; 11905 } 11906 }, 11907 11908 /** 11909 * Renders controls and borders for the object 11910 * @param {CanvasRenderingContext2D} ctx Context to render on 11911 * @param {Boolean} [noTransform] When true, context is not transformed 11912 */ 11913 _renderControls: function(ctx, noTransform) { 11914 if (!this.active || noTransform 11915 || (this.group && this.group !== this.canvas.getActiveGroup())) { 11916 return; 11917 } 11918 11919 var vpt = this.getViewportTransform(), 11920 matrix = this.calcTransformMatrix(), 11921 options; 11922 matrix = fabric.util.multiplyTransformMatrices(vpt, matrix); 11923 options = fabric.util.qrDecompose(matrix); 11924 ctx.save(); 11925 ctx.translate(options.translateX, options.translateY); 11926 if (this.group && this.group === this.canvas.getActiveGroup()) { 11927 ctx.rotate(degreesToRadians(options.angle)); 11928 this.drawBordersInGroup(ctx, options); 11929 } 11930 else { 11931 ctx.rotate(degreesToRadians(this.angle)); 11932 this.drawBorders(ctx); 11933 } 11934 this.drawControls(ctx); 11935 ctx.restore(); 11936 }, 11937 11938 /** 11939 * @private 11940 * @param {CanvasRenderingContext2D} ctx Context to render on 11941 */ 11942 _setShadow: function(ctx) { 11943 if (!this.shadow) { 11944 return; 11945 } 11946 11947 var multX = (this.canvas && this.canvas.viewportTransform[0]) || 1, 11948 multY = (this.canvas && this.canvas.viewportTransform[3]) || 1; 11949 if (this.canvas && this.canvas._isRetinaScaling()) { 11950 multX *= fabric.devicePixelRatio; 11951 multY *= fabric.devicePixelRatio; 11952 } 11953 ctx.shadowColor = this.shadow.color; 11954 ctx.shadowBlur = this.shadow.blur * (multX + multY) * (this.scaleX + this.scaleY) / 4; 11955 ctx.shadowOffsetX = this.shadow.offsetX * multX * this.scaleX; 11956 ctx.shadowOffsetY = this.shadow.offsetY * multY * this.scaleY; 11957 }, 11958 11959 /** 11960 * @private 11961 * @param {CanvasRenderingContext2D} ctx Context to render on 11962 */ 11963 _removeShadow: function(ctx) { 11964 if (!this.shadow) { 11965 return; 11966 } 11967 11968 ctx.shadowColor = ''; 11969 ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; 11970 }, 11971 11972 /** 11973 * @private 11974 * @param {CanvasRenderingContext2D} ctx Context to render on 11975 */ 11976 _renderFill: function(ctx) { 11977 if (!this.fill) { 11978 return; 11979 } 11980 11981 ctx.save(); 11982 if (this.fill.gradientTransform) { 11983 var g = this.fill.gradientTransform; 11984 ctx.transform.apply(ctx, g); 11985 } 11986 if (this.fill.toLive) { 11987 ctx.translate( 11988 -this.width / 2 + this.fill.offsetX || 0, 11989 -this.height / 2 + this.fill.offsetY || 0); 11990 } 11991 if (this.fillRule === 'evenodd') { 11992 ctx.fill('evenodd'); 11993 } 11994 else { 11995 ctx.fill(); 11996 } 11997 ctx.restore(); 11998 }, 11999 12000 /** 12001 * @private 12002 * @param {CanvasRenderingContext2D} ctx Context to render on 12003 */ 12004 _renderStroke: function(ctx) { 12005 if (!this.stroke || this.strokeWidth === 0) { 12006 return; 12007 } 12008 12009 if (this.shadow && !this.shadow.affectStroke) { 12010 this._removeShadow(ctx); 12011 } 12012 12013 ctx.save(); 12014 12015 if (this.strokeDashArray) { 12016 // Spec requires the concatenation of two copies the dash list when the number of elements is odd 12017 if (1 & this.strokeDashArray.length) { 12018 this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray); 12019 } 12020 if (supportsLineDash) { 12021 ctx.setLineDash(this.strokeDashArray); 12022 this._stroke && this._stroke(ctx); 12023 } 12024 else { 12025 this._renderDashedStroke && this._renderDashedStroke(ctx); 12026 } 12027 ctx.stroke(); 12028 } 12029 else { 12030 if (this.stroke.gradientTransform) { 12031 var g = this.stroke.gradientTransform; 12032 ctx.transform.apply(ctx, g); 12033 } 12034 this._stroke ? this._stroke(ctx) : ctx.stroke(); 12035 } 12036 ctx.restore(); 12037 }, 12038 12039 /** 12040 * Clones an instance 12041 * @param {Function} callback Callback is invoked with a clone as a first argument 12042 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 12043 * @return {fabric.Object} clone of an instance 12044 */ 12045 clone: function(callback, propertiesToInclude) { 12046 if (this.constructor.fromObject) { 12047 return this.constructor.fromObject(this.toObject(propertiesToInclude), callback); 12048 } 12049 return new fabric.Object(this.toObject(propertiesToInclude)); 12050 }, 12051 12052 /** 12053 * Creates an instance of fabric.Image out of an object 12054 * @param {Function} callback callback, invoked with an instance as a first argument 12055 * @return {fabric.Object} thisArg 12056 */ 12057 cloneAsImage: function(callback) { 12058 var dataUrl = this.toDataURL(); 12059 fabric.util.loadImage(dataUrl, function(img) { 12060 if (callback) { 12061 callback(new fabric.Image(img)); 12062 } 12063 }); 12064 return this; 12065 }, 12066 12067 /** 12068 * Converts an object into a data-url-like string 12069 * @param {Object} options Options object 12070 * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" 12071 * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. 12072 * @param {Number} [options.multiplier=1] Multiplier to scale by 12073 * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 12074 * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 12075 * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 12076 * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 12077 * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format 12078 */ 12079 toDataURL: function(options) { 12080 options || (options = { }); 12081 12082 var el = fabric.util.createCanvasElement(), 12083 boundingRect = this.getBoundingRect(); 12084 12085 el.width = boundingRect.width; 12086 el.height = boundingRect.height; 12087 12088 fabric.util.wrapElement(el, 'div'); 12089 var canvas = new fabric.StaticCanvas(el); 12090 12091 // to avoid common confusion https://github.com/kangax/fabric.js/issues/806 12092 if (options.format === 'jpg') { 12093 options.format = 'jpeg'; 12094 } 12095 12096 if (options.format === 'jpeg') { 12097 canvas.backgroundColor = '#fff'; 12098 } 12099 12100 var origParams = { 12101 active: this.get('active'), 12102 left: this.getLeft(), 12103 top: this.getTop() 12104 }; 12105 12106 this.set('active', false); 12107 this.setPositionByOrigin(new fabric.Point(el.width / 2, el.height / 2), 'center', 'center'); 12108 12109 var originalCanvas = this.canvas; 12110 canvas.add(this); 12111 var data = canvas.toDataURL(options); 12112 12113 this.set(origParams).setCoords(); 12114 this.canvas = originalCanvas; 12115 12116 canvas.dispose(); 12117 canvas = null; 12118 12119 return data; 12120 }, 12121 12122 /** 12123 * Returns true if specified type is identical to the type of an instance 12124 * @param {String} type Type to check against 12125 * @return {Boolean} 12126 */ 12127 isType: function(type) { 12128 return this.type === type; 12129 }, 12130 12131 /** 12132 * Returns complexity of an instance 12133 * @return {Number} complexity of this instance 12134 */ 12135 complexity: function() { 12136 return 0; 12137 }, 12138 12139 /** 12140 * Returns a JSON representation of an instance 12141 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 12142 * @return {Object} JSON 12143 */ 12144 toJSON: function(propertiesToInclude) { 12145 // delegate, not alias 12146 return this.toObject(propertiesToInclude); 12147 }, 12148 12149 /** 12150 * Sets gradient (fill or stroke) of an object 12151 * <b>Backwards incompatibility note:</b> This method was named "setGradientFill" until v1.1.0 12152 * @param {String} property Property name 'stroke' or 'fill' 12153 * @param {Object} [options] Options object 12154 * @param {String} [options.type] Type of gradient 'radial' or 'linear' 12155 * @param {Number} [options.x1=0] x-coordinate of start point 12156 * @param {Number} [options.y1=0] y-coordinate of start point 12157 * @param {Number} [options.x2=0] x-coordinate of end point 12158 * @param {Number} [options.y2=0] y-coordinate of end point 12159 * @param {Number} [options.r1=0] Radius of start point (only for radial gradients) 12160 * @param {Number} [options.r2=0] Radius of end point (only for radial gradients) 12161 * @param {Object} [options.colorStops] Color stops object eg. {0: 'ff0000', 1: '000000'} 12162 * @param {Object} [options.gradientTransform] transforMatrix for gradient 12163 * @return {fabric.Object} thisArg 12164 * @chainable 12165 * @see {@link http://jsfiddle.net/fabricjs/58y8b/|jsFiddle demo} 12166 * @example <caption>Set linear gradient</caption> 12167 * object.setGradient('fill', { 12168 * type: 'linear', 12169 * x1: -object.width / 2, 12170 * y1: 0, 12171 * x2: object.width / 2, 12172 * y2: 0, 12173 * colorStops: { 12174 * 0: 'red', 12175 * 0.5: '#005555', 12176 * 1: 'rgba(0,0,255,0.5)' 12177 * } 12178 * }); 12179 * canvas.renderAll(); 12180 * @example <caption>Set radial gradient</caption> 12181 * object.setGradient('fill', { 12182 * type: 'radial', 12183 * x1: 0, 12184 * y1: 0, 12185 * x2: 0, 12186 * y2: 0, 12187 * r1: object.width / 2, 12188 * r2: 10, 12189 * colorStops: { 12190 * 0: 'red', 12191 * 0.5: '#005555', 12192 * 1: 'rgba(0,0,255,0.5)' 12193 * } 12194 * }); 12195 * canvas.renderAll(); 12196 */ 12197 setGradient: function(property, options) { 12198 options || (options = { }); 12199 12200 var gradient = { colorStops: [] }; 12201 12202 gradient.type = options.type || (options.r1 || options.r2 ? 'radial' : 'linear'); 12203 gradient.coords = { 12204 x1: options.x1, 12205 y1: options.y1, 12206 x2: options.x2, 12207 y2: options.y2 12208 }; 12209 12210 if (options.r1 || options.r2) { 12211 gradient.coords.r1 = options.r1; 12212 gradient.coords.r2 = options.r2; 12213 } 12214 12215 options.gradientTransform && (gradient.gradientTransform = options.gradientTransform); 12216 12217 for (var position in options.colorStops) { 12218 var color = new fabric.Color(options.colorStops[position]); 12219 gradient.colorStops.push({ 12220 offset: position, 12221 color: color.toRgb(), 12222 opacity: color.getAlpha() 12223 }); 12224 } 12225 12226 return this.set(property, fabric.Gradient.forObject(this, gradient)); 12227 }, 12228 12229 /** 12230 * Sets pattern fill of an object 12231 * @param {Object} options Options object 12232 * @param {(String|HTMLImageElement)} options.source Pattern source 12233 * @param {String} [options.repeat=repeat] Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat) 12234 * @param {Number} [options.offsetX=0] Pattern horizontal offset from object's left/top corner 12235 * @param {Number} [options.offsetY=0] Pattern vertical offset from object's left/top corner 12236 * @return {fabric.Object} thisArg 12237 * @chainable 12238 * @see {@link http://jsfiddle.net/fabricjs/QT3pa/|jsFiddle demo} 12239 * @example <caption>Set pattern</caption> 12240 * fabric.util.loadImage('http://fabricjs.com/assets/escheresque_ste.png', function(img) { 12241 * object.setPatternFill({ 12242 * source: img, 12243 * repeat: 'repeat' 12244 * }); 12245 * canvas.renderAll(); 12246 * }); 12247 */ 12248 setPatternFill: function(options) { 12249 return this.set('fill', new fabric.Pattern(options)); 12250 }, 12251 12252 /** 12253 * Sets {@link fabric.Object#shadow|shadow} of an object 12254 * @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)") 12255 * @param {String} [options.color=rgb(0,0,0)] Shadow color 12256 * @param {Number} [options.blur=0] Shadow blur 12257 * @param {Number} [options.offsetX=0] Shadow horizontal offset 12258 * @param {Number} [options.offsetY=0] Shadow vertical offset 12259 * @return {fabric.Object} thisArg 12260 * @chainable 12261 * @see {@link http://jsfiddle.net/fabricjs/7gvJG/|jsFiddle demo} 12262 * @example <caption>Set shadow with string notation</caption> 12263 * object.setShadow('2px 2px 10px rgba(0,0,0,0.2)'); 12264 * canvas.renderAll(); 12265 * @example <caption>Set shadow with object notation</caption> 12266 * object.setShadow({ 12267 * color: 'red', 12268 * blur: 10, 12269 * offsetX: 20, 12270 * offsetY: 20 12271 * }); 12272 * canvas.renderAll(); 12273 */ 12274 setShadow: function(options) { 12275 return this.set('shadow', options ? new fabric.Shadow(options) : null); 12276 }, 12277 12278 /** 12279 * Sets "color" of an instance (alias of `set('fill', …)`) 12280 * @param {String} color Color value 12281 * @return {fabric.Object} thisArg 12282 * @chainable 12283 */ 12284 setColor: function(color) { 12285 this.set('fill', color); 12286 return this; 12287 }, 12288 12289 /** 12290 * Sets "angle" of an instance 12291 * @param {Number} angle Angle value (in degrees) 12292 * @return {fabric.Object} thisArg 12293 * @chainable 12294 */ 12295 setAngle: function(angle) { 12296 var shouldCenterOrigin = (this.originX !== 'center' || this.originY !== 'center') && this.centeredRotation; 12297 12298 if (shouldCenterOrigin) { 12299 this._setOriginToCenter(); 12300 } 12301 12302 this.set('angle', angle); 12303 12304 if (shouldCenterOrigin) { 12305 this._resetOrigin(); 12306 } 12307 12308 return this; 12309 }, 12310 12311 /** 12312 * Centers object horizontally on canvas to which it was added last. 12313 * You might need to call `setCoords` on an object after centering, to update controls area. 12314 * @return {fabric.Object} thisArg 12315 * @chainable 12316 */ 12317 centerH: function () { 12318 this.canvas.centerObjectH(this); 12319 return this; 12320 }, 12321 12322 /** 12323 * Centers object vertically on canvas to which it was added last. 12324 * You might need to call `setCoords` on an object after centering, to update controls area. 12325 * @return {fabric.Object} thisArg 12326 * @chainable 12327 */ 12328 centerV: function () { 12329 this.canvas.centerObjectV(this); 12330 return this; 12331 }, 12332 12333 /** 12334 * Centers object vertically and horizontally on canvas to which is was added last 12335 * You might need to call `setCoords` on an object after centering, to update controls area. 12336 * @return {fabric.Object} thisArg 12337 * @chainable 12338 */ 12339 center: function () { 12340 this.canvas.centerObject(this); 12341 return this; 12342 }, 12343 12344 /** 12345 * Removes object from canvas to which it was added last 12346 * @return {fabric.Object} thisArg 12347 * @chainable 12348 */ 12349 remove: function() { 12350 this.canvas.remove(this); 12351 return this; 12352 }, 12353 12354 /** 12355 * Returns coordinates of a pointer relative to an object 12356 * @param {Event} e Event to operate upon 12357 * @param {Object} [pointer] Pointer to operate upon (instead of event) 12358 * @return {Object} Coordinates of a pointer (x, y) 12359 */ 12360 getLocalPointer: function(e, pointer) { 12361 pointer = pointer || this.canvas.getPointer(e); 12362 var pClicked = new fabric.Point(pointer.x, pointer.y), 12363 objectLeftTop = this._getLeftTopCoords(); 12364 if (this.angle) { 12365 pClicked = fabric.util.rotatePoint( 12366 pClicked, objectLeftTop, fabric.util.degreesToRadians(-this.angle)); 12367 } 12368 return { 12369 x: pClicked.x - objectLeftTop.x, 12370 y: pClicked.y - objectLeftTop.y 12371 }; 12372 }, 12373 12374 /** 12375 * Sets canvas globalCompositeOperation for specific object 12376 * custom composition operation for the particular object can be specifed using globalCompositeOperation property 12377 * @param {CanvasRenderingContext2D} ctx Rendering canvas context 12378 */ 12379 _setupCompositeOperation: function (ctx) { 12380 if (this.globalCompositeOperation) { 12381 ctx.globalCompositeOperation = this.globalCompositeOperation; 12382 } 12383 } 12384 }); 12385 12386 fabric.util.createAccessors(fabric.Object); 12387 12388 /** 12389 * Alias for {@link fabric.Object.prototype.setAngle} 12390 * @alias rotate -> setAngle 12391 * @memberOf fabric.Object 12392 */ 12393 fabric.Object.prototype.rotate = fabric.Object.prototype.setAngle; 12394 12395 extend(fabric.Object.prototype, fabric.Observable); 12396 12397 /** 12398 * Defines the number of fraction digits to use when serializing object values. 12399 * You can use it to increase/decrease precision of such values like left, top, scaleX, scaleY, etc. 12400 * @static 12401 * @memberOf fabric.Object 12402 * @constant 12403 * @type Number 12404 */ 12405 fabric.Object.NUM_FRACTION_DIGITS = 2; 12406 12407 /** 12408 * Unique id used internally when creating SVG elements 12409 * @static 12410 * @memberOf fabric.Object 12411 * @type Number 12412 */ 12413 fabric.Object.__uid = 0; 12414 12415 })(typeof exports !== 'undefined' ? exports : this); 12416 12417 12418 (function() { 12419 12420 var degreesToRadians = fabric.util.degreesToRadians, 12421 originXOffset = { 12422 left: -0.5, 12423 center: 0, 12424 right: 0.5 12425 }, 12426 originYOffset = { 12427 top: -0.5, 12428 center: 0, 12429 bottom: 0.5 12430 }; 12431 12432 fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { 12433 12434 /** 12435 * Translates the coordinates from origin to center coordinates (based on the object's dimensions) 12436 * @param {fabric.Point} point The point which corresponds to the originX and originY params 12437 * @param {String} fromOriginX Horizontal origin: 'left', 'center' or 'right' 12438 * @param {String} fromOriginY Vertical origin: 'top', 'center' or 'bottom' 12439 * @param {String} toOriginX Horizontal origin: 'left', 'center' or 'right' 12440 * @param {String} toOriginY Vertical origin: 'top', 'center' or 'bottom' 12441 * @return {fabric.Point} 12442 */ 12443 translateToGivenOrigin: function(point, fromOriginX, fromOriginY, toOriginX, toOriginY) { 12444 var x = point.x, 12445 y = point.y, 12446 offsetX = originXOffset[toOriginX] - originXOffset[fromOriginX], 12447 offsetY = originYOffset[toOriginY] - originYOffset[fromOriginY], 12448 dim; 12449 if (offsetX || offsetY) { 12450 dim = this._getTransformedDimensions(); 12451 x = point.x + offsetX * dim.x; 12452 y = point.y + offsetY * dim.y; 12453 } 12454 return new fabric.Point(x, y); 12455 }, 12456 12457 /** 12458 * Translates the coordinates from origin to center coordinates (based on the object's dimensions) 12459 * @param {fabric.Point} point The point which corresponds to the originX and originY params 12460 * @param {String} originX Horizontal origin: 'left', 'center' or 'right' 12461 * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' 12462 * @return {fabric.Point} 12463 */ 12464 translateToCenterPoint: function(point, originX, originY) { 12465 var p = this.translateToGivenOrigin(point, originX, originY, 'center', 'center'); 12466 if (this.angle) { 12467 return fabric.util.rotatePoint(p, point, degreesToRadians(this.angle)); 12468 } 12469 return p; 12470 }, 12471 12472 /** 12473 * Translates the coordinates from center to origin coordinates (based on the object's dimensions) 12474 * @param {fabric.Point} center The point which corresponds to center of the object 12475 * @param {String} originX Horizontal origin: 'left', 'center' or 'right' 12476 * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' 12477 * @return {fabric.Point} 12478 */ 12479 translateToOriginPoint: function(center, originX, originY) { 12480 var p = this.translateToGivenOrigin(center, 'center', 'center', originX, originY); 12481 if (this.angle) { 12482 return fabric.util.rotatePoint(p, center, degreesToRadians(this.angle)); 12483 } 12484 return p; 12485 }, 12486 12487 /** 12488 * Returns the real center coordinates of the object 12489 * @return {fabric.Point} 12490 */ 12491 getCenterPoint: function() { 12492 var leftTop = new fabric.Point(this.left, this.top); 12493 return this.translateToCenterPoint(leftTop, this.originX, this.originY); 12494 }, 12495 12496 /** 12497 * Returns the coordinates of the object based on center coordinates 12498 * @param {fabric.Point} point The point which corresponds to the originX and originY params 12499 * @return {fabric.Point} 12500 */ 12501 // getOriginPoint: function(center) { 12502 // return this.translateToOriginPoint(center, this.originX, this.originY); 12503 // }, 12504 12505 /** 12506 * Returns the coordinates of the object as if it has a different origin 12507 * @param {String} originX Horizontal origin: 'left', 'center' or 'right' 12508 * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' 12509 * @return {fabric.Point} 12510 */ 12511 getPointByOrigin: function(originX, originY) { 12512 var center = this.getCenterPoint(); 12513 return this.translateToOriginPoint(center, originX, originY); 12514 }, 12515 12516 /** 12517 * Returns the point in local coordinates 12518 * @param {fabric.Point} point The point relative to the global coordinate system 12519 * @param {String} originX Horizontal origin: 'left', 'center' or 'right' 12520 * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' 12521 * @return {fabric.Point} 12522 */ 12523 toLocalPoint: function(point, originX, originY) { 12524 var center = this.getCenterPoint(), 12525 p, p2; 12526 12527 if (originX && originY) { 12528 p = this.translateToGivenOrigin(center, 'center', 'center', originX, originY); 12529 } 12530 else { 12531 p = new fabric.Point(this.left, this.top); 12532 } 12533 12534 p2 = new fabric.Point(point.x, point.y); 12535 if (this.angle) { 12536 p2 = fabric.util.rotatePoint(p2, center, -degreesToRadians(this.angle)); 12537 } 12538 return p2.subtractEquals(p); 12539 }, 12540 12541 /** 12542 * Returns the point in global coordinates 12543 * @param {fabric.Point} The point relative to the local coordinate system 12544 * @return {fabric.Point} 12545 */ 12546 // toGlobalPoint: function(point) { 12547 // return fabric.util.rotatePoint(point, this.getCenterPoint(), degreesToRadians(this.angle)).addEquals(new fabric.Point(this.left, this.top)); 12548 // }, 12549 12550 /** 12551 * Sets the position of the object taking into consideration the object's origin 12552 * @param {fabric.Point} pos The new position of the object 12553 * @param {String} originX Horizontal origin: 'left', 'center' or 'right' 12554 * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' 12555 * @return {void} 12556 */ 12557 setPositionByOrigin: function(pos, originX, originY) { 12558 var center = this.translateToCenterPoint(pos, originX, originY), 12559 position = this.translateToOriginPoint(center, this.originX, this.originY); 12560 12561 this.set('left', position.x); 12562 this.set('top', position.y); 12563 }, 12564 12565 /** 12566 * @param {String} to One of 'left', 'center', 'right' 12567 */ 12568 adjustPosition: function(to) { 12569 var angle = degreesToRadians(this.angle), 12570 hypotFull = this.getWidth(), 12571 xFull = Math.cos(angle) * hypotFull, 12572 yFull = Math.sin(angle) * hypotFull; 12573 12574 //TODO: this function does not consider mixed situation like top, center. 12575 this.left += xFull * (originXOffset[to] - originXOffset[this.originX]); 12576 this.top += yFull * (originXOffset[to] - originXOffset[this.originX]); 12577 12578 this.setCoords(); 12579 this.originX = to; 12580 }, 12581 12582 /** 12583 * Sets the origin/position of the object to it's center point 12584 * @private 12585 * @return {void} 12586 */ 12587 _setOriginToCenter: function() { 12588 this._originalOriginX = this.originX; 12589 this._originalOriginY = this.originY; 12590 12591 var center = this.getCenterPoint(); 12592 12593 this.originX = 'center'; 12594 this.originY = 'center'; 12595 12596 this.left = center.x; 12597 this.top = center.y; 12598 }, 12599 12600 /** 12601 * Resets the origin/position of the object to it's original origin 12602 * @private 12603 * @return {void} 12604 */ 12605 _resetOrigin: function() { 12606 var originPoint = this.translateToOriginPoint( 12607 this.getCenterPoint(), 12608 this._originalOriginX, 12609 this._originalOriginY); 12610 12611 this.originX = this._originalOriginX; 12612 this.originY = this._originalOriginY; 12613 12614 this.left = originPoint.x; 12615 this.top = originPoint.y; 12616 12617 this._originalOriginX = null; 12618 this._originalOriginY = null; 12619 }, 12620 12621 /** 12622 * @private 12623 */ 12624 _getLeftTopCoords: function() { 12625 return this.translateToOriginPoint(this.getCenterPoint(), 'left', 'top'); 12626 } 12627 }); 12628 12629 })(); 12630 12631 12632 (function() { 12633 12634 function getCoords(oCoords) { 12635 return [ 12636 new fabric.Point(oCoords.tl.x, oCoords.tl.y), 12637 new fabric.Point(oCoords.tr.x, oCoords.tr.y), 12638 new fabric.Point(oCoords.br.x, oCoords.br.y), 12639 new fabric.Point(oCoords.bl.x, oCoords.bl.y) 12640 ]; 12641 } 12642 12643 var degreesToRadians = fabric.util.degreesToRadians, 12644 multiplyMatrices = fabric.util.multiplyTransformMatrices; 12645 12646 fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { 12647 12648 /** 12649 * Object containing coordinates of object's controls 12650 * @type Object 12651 * @default 12652 */ 12653 oCoords: null, 12654 12655 /** 12656 * Checks if object intersects with an area formed by 2 points 12657 * @param {Object} pointTL top-left point of area 12658 * @param {Object} pointBR bottom-right point of area 12659 * @return {Boolean} true if object intersects with an area formed by 2 points 12660 */ 12661 intersectsWithRect: function(pointTL, pointBR) { 12662 var oCoords = getCoords(this.oCoords), 12663 intersection = fabric.Intersection.intersectPolygonRectangle( 12664 oCoords, 12665 pointTL, 12666 pointBR 12667 ); 12668 return intersection.status === 'Intersection'; 12669 }, 12670 12671 /** 12672 * Checks if object intersects with another object 12673 * @param {Object} other Object to test 12674 * @return {Boolean} true if object intersects with another object 12675 */ 12676 intersectsWithObject: function(other) { 12677 var intersection = fabric.Intersection.intersectPolygonPolygon( 12678 getCoords(this.oCoords), 12679 getCoords(other.oCoords) 12680 ); 12681 12682 return intersection.status === 'Intersection'; 12683 }, 12684 12685 /** 12686 * Checks if object is fully contained within area of another object 12687 * @param {Object} other Object to test 12688 * @return {Boolean} true if object is fully contained within area of another object 12689 */ 12690 isContainedWithinObject: function(other) { 12691 var boundingRect = other.getBoundingRect(), 12692 point1 = new fabric.Point(boundingRect.left, boundingRect.top), 12693 point2 = new fabric.Point(boundingRect.left + boundingRect.width, boundingRect.top + boundingRect.height); 12694 12695 return this.isContainedWithinRect(point1, point2); 12696 }, 12697 12698 /** 12699 * Checks if object is fully contained within area formed by 2 points 12700 * @param {Object} pointTL top-left point of area 12701 * @param {Object} pointBR bottom-right point of area 12702 * @return {Boolean} true if object is fully contained within area formed by 2 points 12703 */ 12704 isContainedWithinRect: function(pointTL, pointBR) { 12705 var boundingRect = this.getBoundingRect(); 12706 12707 return ( 12708 boundingRect.left >= pointTL.x && 12709 boundingRect.left + boundingRect.width <= pointBR.x && 12710 boundingRect.top >= pointTL.y && 12711 boundingRect.top + boundingRect.height <= pointBR.y 12712 ); 12713 }, 12714 12715 /** 12716 * Checks if point is inside the object 12717 * @param {fabric.Point} point Point to check against 12718 * @return {Boolean} true if point is inside the object 12719 */ 12720 containsPoint: function(point) { 12721 var lines = this._getImageLines(this.oCoords), 12722 xPoints = this._findCrossPoints(point, lines); 12723 12724 // if xPoints is odd then point is inside the object 12725 return (xPoints !== 0 && xPoints % 2 === 1); 12726 }, 12727 12728 /** 12729 * Method that returns an object with the object edges in it, given the coordinates of the corners 12730 * @private 12731 * @param {Object} oCoords Coordinates of the object corners 12732 */ 12733 _getImageLines: function(oCoords) { 12734 return { 12735 topline: { 12736 o: oCoords.tl, 12737 d: oCoords.tr 12738 }, 12739 rightline: { 12740 o: oCoords.tr, 12741 d: oCoords.br 12742 }, 12743 bottomline: { 12744 o: oCoords.br, 12745 d: oCoords.bl 12746 }, 12747 leftline: { 12748 o: oCoords.bl, 12749 d: oCoords.tl 12750 } 12751 }; 12752 }, 12753 12754 /** 12755 * Helper method to determine how many cross points are between the 4 object edges 12756 * and the horizontal line determined by a point on canvas 12757 * @private 12758 * @param {fabric.Point} point Point to check 12759 * @param {Object} oCoords Coordinates of the object being evaluated 12760 */ 12761 _findCrossPoints: function(point, oCoords) { 12762 var b1, b2, a1, a2, xi, yi, 12763 xcount = 0, 12764 iLine; 12765 12766 for (var lineKey in oCoords) { 12767 iLine = oCoords[lineKey]; 12768 // optimisation 1: line below point. no cross 12769 if ((iLine.o.y < point.y) && (iLine.d.y < point.y)) { 12770 continue; 12771 } 12772 // optimisation 2: line above point. no cross 12773 if ((iLine.o.y >= point.y) && (iLine.d.y >= point.y)) { 12774 continue; 12775 } 12776 // optimisation 3: vertical line case 12777 if ((iLine.o.x === iLine.d.x) && (iLine.o.x >= point.x)) { 12778 xi = iLine.o.x; 12779 yi = point.y; 12780 } 12781 // calculate the intersection point 12782 else { 12783 b1 = 0; 12784 b2 = (iLine.d.y - iLine.o.y) / (iLine.d.x - iLine.o.x); 12785 a1 = point.y - b1 * point.x; 12786 a2 = iLine.o.y - b2 * iLine.o.x; 12787 12788 xi = - (a1 - a2) / (b1 - b2); 12789 yi = a1 + b1 * xi; 12790 } 12791 // dont count xi < point.x cases 12792 if (xi >= point.x) { 12793 xcount += 1; 12794 } 12795 // optimisation 4: specific for square images 12796 if (xcount === 2) { 12797 break; 12798 } 12799 } 12800 return xcount; 12801 }, 12802 12803 /** 12804 * Returns width of an object's bounding rectangle 12805 * @deprecated since 1.0.4 12806 * @return {Number} width value 12807 */ 12808 getBoundingRectWidth: function() { 12809 return this.getBoundingRect().width; 12810 }, 12811 12812 /** 12813 * Returns height of an object's bounding rectangle 12814 * @deprecated since 1.0.4 12815 * @return {Number} height value 12816 */ 12817 getBoundingRectHeight: function() { 12818 return this.getBoundingRect().height; 12819 }, 12820 12821 /** 12822 * Returns coordinates of object's bounding rectangle (left, top, width, height) 12823 * @return {Object} Object with left, top, width, height properties 12824 */ 12825 getBoundingRect: function() { 12826 this.oCoords || this.setCoords(); 12827 return fabric.util.makeBoundingBoxFromPoints([ 12828 this.oCoords.tl, 12829 this.oCoords.tr, 12830 this.oCoords.br, 12831 this.oCoords.bl 12832 ]); 12833 }, 12834 12835 /** 12836 * Returns width of an object 12837 * @return {Number} width value 12838 */ 12839 getWidth: function() { 12840 //needs to be changed 12841 return this._getTransformedDimensions().x; 12842 }, 12843 12844 /** 12845 * Returns height of an object 12846 * @return {Number} height value 12847 */ 12848 getHeight: function() { 12849 //needs to be changed 12850 return this._getTransformedDimensions().y; 12851 }, 12852 12853 /** 12854 * Makes sure the scale is valid and modifies it if necessary 12855 * @private 12856 * @param {Number} value 12857 * @return {Number} 12858 */ 12859 _constrainScale: function(value) { 12860 if (Math.abs(value) < this.minScaleLimit) { 12861 if (value < 0) { 12862 return -this.minScaleLimit; 12863 } 12864 else { 12865 return this.minScaleLimit; 12866 } 12867 } 12868 return value; 12869 }, 12870 12871 /** 12872 * Scales an object (equally by x and y) 12873 * @param {Number} value Scale factor 12874 * @return {fabric.Object} thisArg 12875 * @chainable 12876 */ 12877 scale: function(value) { 12878 value = this._constrainScale(value); 12879 12880 if (value < 0) { 12881 this.flipX = !this.flipX; 12882 this.flipY = !this.flipY; 12883 value *= -1; 12884 } 12885 12886 this.scaleX = value; 12887 this.scaleY = value; 12888 this.setCoords(); 12889 return this; 12890 }, 12891 12892 /** 12893 * Scales an object to a given width, with respect to bounding box (scaling by x/y equally) 12894 * @param {Number} value New width value 12895 * @return {fabric.Object} thisArg 12896 * @chainable 12897 */ 12898 scaleToWidth: function(value) { 12899 // adjust to bounding rect factor so that rotated shapes would fit as well 12900 var boundingRectFactor = this.getBoundingRect().width / this.getWidth(); 12901 return this.scale(value / this.width / boundingRectFactor); 12902 }, 12903 12904 /** 12905 * Scales an object to a given height, with respect to bounding box (scaling by x/y equally) 12906 * @param {Number} value New height value 12907 * @return {fabric.Object} thisArg 12908 * @chainable 12909 */ 12910 scaleToHeight: function(value) { 12911 // adjust to bounding rect factor so that rotated shapes would fit as well 12912 var boundingRectFactor = this.getBoundingRect().height / this.getHeight(); 12913 return this.scale(value / this.height / boundingRectFactor); 12914 }, 12915 12916 /** 12917 * Sets corner position coordinates based on current angle, width and height 12918 * See https://github.com/kangax/fabric.js/wiki/When-to-call-setCoords 12919 * @return {fabric.Object} thisArg 12920 * @chainable 12921 */ 12922 setCoords: function() { 12923 var theta = degreesToRadians(this.angle), 12924 vpt = this.getViewportTransform(), 12925 dim = this._calculateCurrentDimensions(), 12926 currentWidth = dim.x, currentHeight = dim.y; 12927 12928 // If width is negative, make postive. Fixes path selection issue 12929 if (currentWidth < 0) { 12930 currentWidth = Math.abs(currentWidth); 12931 } 12932 12933 var sinTh = Math.sin(theta), 12934 cosTh = Math.cos(theta), 12935 _angle = currentWidth > 0 ? Math.atan(currentHeight / currentWidth) : 0, 12936 _hypotenuse = (currentWidth / Math.cos(_angle)) / 2, 12937 offsetX = Math.cos(_angle + theta) * _hypotenuse, 12938 offsetY = Math.sin(_angle + theta) * _hypotenuse, 12939 12940 // offset added for rotate and scale actions 12941 coords = fabric.util.transformPoint(this.getCenterPoint(), vpt), 12942 tl = new fabric.Point(coords.x - offsetX, coords.y - offsetY), 12943 tr = new fabric.Point(tl.x + (currentWidth * cosTh), tl.y + (currentWidth * sinTh)), 12944 bl = new fabric.Point(tl.x - (currentHeight * sinTh), tl.y + (currentHeight * cosTh)), 12945 br = new fabric.Point(coords.x + offsetX, coords.y + offsetY), 12946 ml = new fabric.Point((tl.x + bl.x)/2, (tl.y + bl.y)/2), 12947 mt = new fabric.Point((tr.x + tl.x)/2, (tr.y + tl.y)/2), 12948 mr = new fabric.Point((br.x + tr.x)/2, (br.y + tr.y)/2), 12949 mb = new fabric.Point((br.x + bl.x)/2, (br.y + bl.y)/2), 12950 mtr = new fabric.Point(mt.x + sinTh * this.rotatingPointOffset, mt.y - cosTh * this.rotatingPointOffset); 12951 // debugging 12952 12953 /* setTimeout(function() { 12954 canvas.contextTop.fillStyle = 'green'; 12955 canvas.contextTop.fillRect(mb.x, mb.y, 3, 3); 12956 canvas.contextTop.fillRect(bl.x, bl.y, 3, 3); 12957 canvas.contextTop.fillRect(br.x, br.y, 3, 3); 12958 canvas.contextTop.fillRect(tl.x, tl.y, 3, 3); 12959 canvas.contextTop.fillRect(tr.x, tr.y, 3, 3); 12960 canvas.contextTop.fillRect(ml.x, ml.y, 3, 3); 12961 canvas.contextTop.fillRect(mr.x, mr.y, 3, 3); 12962 canvas.contextTop.fillRect(mt.x, mt.y, 3, 3); 12963 canvas.contextTop.fillRect(mtr.x, mtr.y, 3, 3); 12964 }, 50); */ 12965 12966 this.oCoords = { 12967 // corners 12968 tl: tl, tr: tr, br: br, bl: bl, 12969 // middle 12970 ml: ml, mt: mt, mr: mr, mb: mb, 12971 // rotating point 12972 mtr: mtr 12973 }; 12974 12975 // set coordinates of the draggable boxes in the corners used to scale/rotate the image 12976 this._setCornerCoords && this._setCornerCoords(); 12977 12978 return this; 12979 }, 12980 12981 _calcRotateMatrix: function() { 12982 if (this.angle) { 12983 var theta = degreesToRadians(this.angle), cos = Math.cos(theta), sin = Math.sin(theta); 12984 return [cos, sin, -sin, cos, 0, 0]; 12985 } 12986 return [1, 0, 0, 1, 0, 0]; 12987 }, 12988 12989 calcTransformMatrix: function() { 12990 var center = this.getCenterPoint(), 12991 translateMatrix = [1, 0, 0, 1, center.x, center.y], 12992 rotateMatrix = this._calcRotateMatrix(), 12993 dimensionMatrix = this._calcDimensionsTransformMatrix(this.skewX, this.skewY, true), 12994 matrix = this.group ? this.group.calcTransformMatrix() : [1, 0, 0, 1, 0, 0]; 12995 matrix = multiplyMatrices(matrix, translateMatrix); 12996 matrix = multiplyMatrices(matrix, rotateMatrix); 12997 matrix = multiplyMatrices(matrix, dimensionMatrix); 12998 return matrix; 12999 }, 13000 13001 _calcDimensionsTransformMatrix: function(skewX, skewY, flipping) { 13002 var skewMatrixX = [1, 0, Math.tan(degreesToRadians(skewX)), 1], 13003 skewMatrixY = [1, Math.tan(degreesToRadians(skewY)), 0, 1], 13004 scaleX = this.scaleX * (flipping && this.flipX ? -1 : 1), 13005 scaleY = this.scaleY * (flipping && this.flipY ? -1 : 1), 13006 scaleMatrix = [scaleX, 0, 0, scaleY], 13007 m = multiplyMatrices(scaleMatrix, skewMatrixX, true); 13008 return multiplyMatrices(m, skewMatrixY, true); 13009 } 13010 }); 13011 })(); 13012 13013 13014 fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { 13015 13016 /** 13017 * Moves an object to the bottom of the stack of drawn objects 13018 * @return {fabric.Object} thisArg 13019 * @chainable 13020 */ 13021 sendToBack: function() { 13022 if (this.group) { 13023 fabric.StaticCanvas.prototype.sendToBack.call(this.group, this); 13024 } 13025 else { 13026 this.canvas.sendToBack(this); 13027 } 13028 return this; 13029 }, 13030 13031 /** 13032 * Moves an object to the top of the stack of drawn objects 13033 * @return {fabric.Object} thisArg 13034 * @chainable 13035 */ 13036 bringToFront: function() { 13037 if (this.group) { 13038 fabric.StaticCanvas.prototype.bringToFront.call(this.group, this); 13039 } 13040 else { 13041 this.canvas.bringToFront(this); 13042 } 13043 return this; 13044 }, 13045 13046 /** 13047 * Moves an object down in stack of drawn objects 13048 * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object 13049 * @return {fabric.Object} thisArg 13050 * @chainable 13051 */ 13052 sendBackwards: function(intersecting) { 13053 if (this.group) { 13054 fabric.StaticCanvas.prototype.sendBackwards.call(this.group, this, intersecting); 13055 } 13056 else { 13057 this.canvas.sendBackwards(this, intersecting); 13058 } 13059 return this; 13060 }, 13061 13062 /** 13063 * Moves an object up in stack of drawn objects 13064 * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object 13065 * @return {fabric.Object} thisArg 13066 * @chainable 13067 */ 13068 bringForward: function(intersecting) { 13069 if (this.group) { 13070 fabric.StaticCanvas.prototype.bringForward.call(this.group, this, intersecting); 13071 } 13072 else { 13073 this.canvas.bringForward(this, intersecting); 13074 } 13075 return this; 13076 }, 13077 13078 /** 13079 * Moves an object to specified level in stack of drawn objects 13080 * @param {Number} index New position of object 13081 * @return {fabric.Object} thisArg 13082 * @chainable 13083 */ 13084 moveTo: function(index) { 13085 if (this.group) { 13086 fabric.StaticCanvas.prototype.moveTo.call(this.group, this, index); 13087 } 13088 else { 13089 this.canvas.moveTo(this, index); 13090 } 13091 return this; 13092 } 13093 }); 13094 13095 13096 /* _TO_SVG_START_ */ 13097 fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { 13098 13099 /** 13100 * Returns styles-string for svg-export 13101 * @param {Boolean} skipShadow a boolean to skip shadow filter output 13102 * @return {String} 13103 */ 13104 getSvgStyles: function(skipShadow) { 13105 13106 var fill = this.fill 13107 ? (this.fill.toLive ? 'url(#SVGID_' + this.fill.id + ')' : this.fill) 13108 : 'none', 13109 fillRule = this.fillRule, 13110 stroke = this.stroke 13111 ? (this.stroke.toLive ? 'url(#SVGID_' + this.stroke.id + ')' : this.stroke) 13112 : 'none', 13113 13114 strokeWidth = this.strokeWidth ? this.strokeWidth : '0', 13115 strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(' ') : 'none', 13116 strokeLineCap = this.strokeLineCap ? this.strokeLineCap : 'butt', 13117 strokeLineJoin = this.strokeLineJoin ? this.strokeLineJoin : 'miter', 13118 strokeMiterLimit = this.strokeMiterLimit ? this.strokeMiterLimit : '4', 13119 opacity = typeof this.opacity !== 'undefined' ? this.opacity : '1', 13120 13121 visibility = this.visible ? '' : ' visibility: hidden;', 13122 filter = skipShadow ? '' : this.getSvgFilter(); 13123 13124 return [ 13125 'stroke: ', stroke, '; ', 13126 'stroke-width: ', strokeWidth, '; ', 13127 'stroke-dasharray: ', strokeDashArray, '; ', 13128 'stroke-linecap: ', strokeLineCap, '; ', 13129 'stroke-linejoin: ', strokeLineJoin, '; ', 13130 'stroke-miterlimit: ', strokeMiterLimit, '; ', 13131 'fill: ', fill, '; ', 13132 'fill-rule: ', fillRule, '; ', 13133 'opacity: ', opacity, ';', 13134 filter, 13135 visibility 13136 ].join(''); 13137 }, 13138 13139 /** 13140 * Returns filter for svg shadow 13141 * @return {String} 13142 */ 13143 getSvgFilter: function() { 13144 return this.shadow ? 'filter: url(#SVGID_' + this.shadow.id + ');' : ''; 13145 }, 13146 13147 /** 13148 * Returns transform-string for svg-export 13149 * @return {String} 13150 */ 13151 getSvgTransform: function() { 13152 if (this.group && this.group.type === 'path-group') { 13153 return ''; 13154 } 13155 var toFixed = fabric.util.toFixed, 13156 angle = this.getAngle(), 13157 skewX = (this.getSkewX() % 360), 13158 skewY = (this.getSkewY() % 360), 13159 center = this.getCenterPoint(), 13160 13161 NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, 13162 13163 translatePart = this.type === 'path-group' ? '' : 'translate(' + 13164 toFixed(center.x, NUM_FRACTION_DIGITS) + 13165 ' ' + 13166 toFixed(center.y, NUM_FRACTION_DIGITS) + 13167 ')', 13168 13169 anglePart = angle !== 0 13170 ? (' rotate(' + toFixed(angle, NUM_FRACTION_DIGITS) + ')') 13171 : '', 13172 13173 scalePart = (this.scaleX === 1 && this.scaleY === 1) 13174 ? '' : 13175 (' scale(' + 13176 toFixed(this.scaleX, NUM_FRACTION_DIGITS) + 13177 ' ' + 13178 toFixed(this.scaleY, NUM_FRACTION_DIGITS) + 13179 ')'), 13180 13181 skewXPart = skewX !== 0 ? ' skewX(' + toFixed(skewX, NUM_FRACTION_DIGITS) + ')' : '', 13182 13183 skewYPart = skewY !== 0 ? ' skewY(' + toFixed(skewY, NUM_FRACTION_DIGITS) + ')' : '', 13184 13185 addTranslateX = this.type === 'path-group' ? this.width : 0, 13186 13187 flipXPart = this.flipX ? ' matrix(-1 0 0 1 ' + addTranslateX + ' 0) ' : '', 13188 13189 addTranslateY = this.type === 'path-group' ? this.height : 0, 13190 13191 flipYPart = this.flipY ? ' matrix(1 0 0 -1 0 ' + addTranslateY + ')' : ''; 13192 13193 return [ 13194 translatePart, anglePart, scalePart, flipXPart, flipYPart, skewXPart, skewYPart 13195 ].join(''); 13196 }, 13197 13198 /** 13199 * Returns transform-string for svg-export from the transform matrix of single elements 13200 * @return {String} 13201 */ 13202 getSvgTransformMatrix: function() { 13203 return this.transformMatrix ? ' matrix(' + this.transformMatrix.join(' ') + ') ' : ''; 13204 }, 13205 13206 /** 13207 * @private 13208 */ 13209 _createBaseSVGMarkup: function() { 13210 var markup = [ ]; 13211 13212 if (this.fill && this.fill.toLive) { 13213 markup.push(this.fill.toSVG(this, false)); 13214 } 13215 if (this.stroke && this.stroke.toLive) { 13216 markup.push(this.stroke.toSVG(this, false)); 13217 } 13218 if (this.shadow) { 13219 markup.push(this.shadow.toSVG(this)); 13220 } 13221 return markup; 13222 } 13223 }); 13224 /* _TO_SVG_END_ */ 13225 13226 13227 /* 13228 Depends on `stateProperties` 13229 */ 13230 fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { 13231 13232 /** 13233 * Returns true if object state (one of its state properties) was changed 13234 * @return {Boolean} true if instance' state has changed since `{@link fabric.Object#saveState}` was called 13235 */ 13236 hasStateChanged: function() { 13237 return this.stateProperties.some(function(prop) { 13238 return this.get(prop) !== this.originalState[prop]; 13239 }, this); 13240 }, 13241 13242 /** 13243 * Saves state of an object 13244 * @param {Object} [options] Object with additional `stateProperties` array to include when saving state 13245 * @return {fabric.Object} thisArg 13246 */ 13247 saveState: function(options) { 13248 this.stateProperties.forEach(function(prop) { 13249 this.originalState[prop] = this.get(prop); 13250 }, this); 13251 13252 if (options && options.stateProperties) { 13253 options.stateProperties.forEach(function(prop) { 13254 this.originalState[prop] = this.get(prop); 13255 }, this); 13256 } 13257 13258 return this; 13259 }, 13260 13261 /** 13262 * Setups state of an object 13263 * @return {fabric.Object} thisArg 13264 */ 13265 setupState: function() { 13266 this.originalState = { }; 13267 this.saveState(); 13268 13269 return this; 13270 } 13271 }); 13272 13273 13274 (function() { 13275 13276 var degreesToRadians = fabric.util.degreesToRadians, 13277 //jscs:disable requireCamelCaseOrUpperCaseIdentifiers 13278 isVML = function() { return typeof G_vmlCanvasManager !== 'undefined'; }; 13279 //jscs:enable requireCamelCaseOrUpperCaseIdentifiers 13280 13281 fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { 13282 13283 /** 13284 * The object interactivity controls. 13285 * @private 13286 */ 13287 _controlsVisibility: null, 13288 13289 /** 13290 * Determines which corner has been clicked 13291 * @private 13292 * @param {Object} pointer The pointer indicating the mouse position 13293 * @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found 13294 */ 13295 _findTargetCorner: function(pointer) { 13296 if (!this.hasControls || !this.active) { 13297 return false; 13298 } 13299 13300 var ex = pointer.x, 13301 ey = pointer.y, 13302 xPoints, 13303 lines; 13304 this.__corner = 0; 13305 for (var i in this.oCoords) { 13306 13307 if (!this.isControlVisible(i)) { 13308 continue; 13309 } 13310 13311 if (i === 'mtr' && !this.hasRotatingPoint) { 13312 continue; 13313 } 13314 13315 if (this.get('lockUniScaling') && 13316 (i === 'mt' || i === 'mr' || i === 'mb' || i === 'ml')) { 13317 continue; 13318 } 13319 13320 lines = this._getImageLines(this.oCoords[i].corner); 13321 13322 // debugging 13323 13324 // canvas.contextTop.fillRect(lines.bottomline.d.x, lines.bottomline.d.y, 2, 2); 13325 // canvas.contextTop.fillRect(lines.bottomline.o.x, lines.bottomline.o.y, 2, 2); 13326 13327 // canvas.contextTop.fillRect(lines.leftline.d.x, lines.leftline.d.y, 2, 2); 13328 // canvas.contextTop.fillRect(lines.leftline.o.x, lines.leftline.o.y, 2, 2); 13329 13330 // canvas.contextTop.fillRect(lines.topline.d.x, lines.topline.d.y, 2, 2); 13331 // canvas.contextTop.fillRect(lines.topline.o.x, lines.topline.o.y, 2, 2); 13332 13333 // canvas.contextTop.fillRect(lines.rightline.d.x, lines.rightline.d.y, 2, 2); 13334 // canvas.contextTop.fillRect(lines.rightline.o.x, lines.rightline.o.y, 2, 2); 13335 13336 xPoints = this._findCrossPoints({ x: ex, y: ey }, lines); 13337 if (xPoints !== 0 && xPoints % 2 === 1) { 13338 this.__corner = i; 13339 return i; 13340 } 13341 } 13342 return false; 13343 }, 13344 13345 /** 13346 * Sets the coordinates of the draggable boxes in the corners of 13347 * the image used to scale/rotate it. 13348 * @private 13349 */ 13350 _setCornerCoords: function() { 13351 var coords = this.oCoords, 13352 newTheta = degreesToRadians(45 - this.angle), 13353 /* Math.sqrt(2 * Math.pow(this.cornerSize, 2)) / 2, */ 13354 /* 0.707106 stands for sqrt(2)/2 */ 13355 cornerHypotenuse = this.cornerSize * 0.707106, 13356 cosHalfOffset = cornerHypotenuse * Math.cos(newTheta), 13357 sinHalfOffset = cornerHypotenuse * Math.sin(newTheta), 13358 x, y; 13359 13360 for (var point in coords) { 13361 x = coords[point].x; 13362 y = coords[point].y; 13363 coords[point].corner = { 13364 tl: { 13365 x: x - sinHalfOffset, 13366 y: y - cosHalfOffset 13367 }, 13368 tr: { 13369 x: x + cosHalfOffset, 13370 y: y - sinHalfOffset 13371 }, 13372 bl: { 13373 x: x - cosHalfOffset, 13374 y: y + sinHalfOffset 13375 }, 13376 br: { 13377 x: x + sinHalfOffset, 13378 y: y + cosHalfOffset 13379 } 13380 }; 13381 } 13382 }, 13383 13384 /* 13385 * Calculate object dimensions from its properties 13386 * @private 13387 */ 13388 _getNonTransformedDimensions: function() { 13389 var strokeWidth = this.strokeWidth, 13390 w = this.width, 13391 h = this.height, 13392 addStrokeToW = true, 13393 addStrokeToH = true; 13394 13395 if (this.type === 'line' && this.strokeLineCap === 'butt') { 13396 addStrokeToH = w; 13397 addStrokeToW = h; 13398 } 13399 13400 if (addStrokeToH) { 13401 h += h < 0 ? -strokeWidth : strokeWidth; 13402 } 13403 13404 if (addStrokeToW) { 13405 w += w < 0 ? -strokeWidth : strokeWidth; 13406 } 13407 13408 return { x: w, y: h }; 13409 }, 13410 13411 /* 13412 * @private 13413 */ 13414 _getTransformedDimensions: function(skewX, skewY) { 13415 if (typeof skewX === 'undefined') { 13416 skewX = this.skewX; 13417 } 13418 if (typeof skewY === 'undefined') { 13419 skewY = this.skewY; 13420 } 13421 var dimensions = this._getNonTransformedDimensions(), 13422 dimX = dimensions.x /2, dimY = dimensions.y / 2, 13423 points = [ 13424 { 13425 x: -dimX, 13426 y: -dimY 13427 }, 13428 { 13429 x: dimX, 13430 y: -dimY 13431 }, 13432 { 13433 x: -dimX, 13434 y: dimY 13435 }, 13436 { 13437 x: dimX, 13438 y: dimY 13439 }], 13440 i, transformMatrix = this._calcDimensionsTransformMatrix(skewX, skewY, false), 13441 bbox; 13442 for (i = 0; i < points.length; i++) { 13443 points[i] = fabric.util.transformPoint(points[i], transformMatrix); 13444 } 13445 bbox = fabric.util.makeBoundingBoxFromPoints(points); 13446 return { x: bbox.width, y: bbox.height }; 13447 }, 13448 13449 /* 13450 * private 13451 */ 13452 _calculateCurrentDimensions: function() { 13453 var vpt = this.getViewportTransform(), 13454 dim = this._getTransformedDimensions(), 13455 w = dim.x, h = dim.y; 13456 13457 w += 2 * this.padding; 13458 h += 2 * this.padding; 13459 13460 return fabric.util.transformPoint(new fabric.Point(w, h), vpt, true); 13461 }, 13462 13463 /** 13464 * Draws borders of an object's bounding box. 13465 * Requires public properties: width, height 13466 * Requires public options: padding, borderColor 13467 * @param {CanvasRenderingContext2D} ctx Context to draw on 13468 * @return {fabric.Object} thisArg 13469 * @chainable 13470 */ 13471 drawBorders: function(ctx) { 13472 if (!this.hasBorders) { 13473 return this; 13474 } 13475 13476 var wh = this._calculateCurrentDimensions(), 13477 strokeWidth = 1 / this.borderScaleFactor, 13478 width = wh.x + strokeWidth, 13479 height = wh.y + strokeWidth; 13480 13481 ctx.save(); 13482 ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; 13483 ctx.strokeStyle = this.borderColor; 13484 ctx.lineWidth = strokeWidth; 13485 13486 ctx.strokeRect( 13487 -width / 2, 13488 -height / 2, 13489 width, 13490 height 13491 ); 13492 13493 if (this.hasRotatingPoint && this.isControlVisible('mtr') && !this.get('lockRotation') && this.hasControls) { 13494 13495 var rotateHeight = -height / 2; 13496 13497 ctx.beginPath(); 13498 ctx.moveTo(0, rotateHeight); 13499 ctx.lineTo(0, rotateHeight - this.rotatingPointOffset); 13500 ctx.closePath(); 13501 ctx.stroke(); 13502 } 13503 13504 ctx.restore(); 13505 return this; 13506 }, 13507 13508 /** 13509 * Draws borders of an object's bounding box when it is inside a group. 13510 * Requires public properties: width, height 13511 * Requires public options: padding, borderColor 13512 * @param {CanvasRenderingContext2D} ctx Context to draw on 13513 * @param {object} options object representing current object parameters 13514 * @return {fabric.Object} thisArg 13515 * @chainable 13516 */ 13517 drawBordersInGroup: function(ctx, options) { 13518 if (!this.hasBorders) { 13519 return this; 13520 } 13521 13522 var p = this._getNonTransformedDimensions(), 13523 matrix = fabric.util.customTransformMatrix(options.scaleX, options.scaleY, options.skewX), 13524 wh = fabric.util.transformPoint(p, matrix), 13525 strokeWidth = 1 / this.borderScaleFactor, 13526 width = wh.x + strokeWidth + 2 * this.padding, 13527 height = wh.y + strokeWidth + 2 * this.padding; 13528 13529 ctx.save(); 13530 13531 ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; 13532 ctx.strokeStyle = this.borderColor; 13533 ctx.lineWidth = strokeWidth; 13534 13535 ctx.strokeRect( 13536 -width / 2, 13537 -height / 2, 13538 width, 13539 height 13540 ); 13541 13542 ctx.restore(); 13543 return this; 13544 }, 13545 13546 /** 13547 * Draws corners of an object's bounding box. 13548 * Requires public properties: width, height 13549 * Requires public options: cornerSize, padding 13550 * @param {CanvasRenderingContext2D} ctx Context to draw on 13551 * @return {fabric.Object} thisArg 13552 * @chainable 13553 */ 13554 drawControls: function(ctx) { 13555 if (!this.hasControls) { 13556 return this; 13557 } 13558 13559 var wh = this._calculateCurrentDimensions(), 13560 width = wh.x, 13561 height = wh.y, 13562 scaleOffset = this.cornerSize, 13563 left = -(width + scaleOffset) / 2, 13564 top = -(height + scaleOffset) / 2, 13565 methodName = this.transparentCorners ? 'strokeRect' : 'fillRect'; 13566 13567 ctx.save(); 13568 13569 ctx.lineWidth = 1; 13570 13571 ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; 13572 ctx.strokeStyle = ctx.fillStyle = this.cornerColor; 13573 13574 // top-left 13575 this._drawControl('tl', ctx, methodName, 13576 left, 13577 top); 13578 13579 // top-right 13580 this._drawControl('tr', ctx, methodName, 13581 left + width, 13582 top); 13583 13584 // bottom-left 13585 this._drawControl('bl', ctx, methodName, 13586 left, 13587 top + height); 13588 13589 // bottom-right 13590 this._drawControl('br', ctx, methodName, 13591 left + width, 13592 top + height); 13593 13594 if (!this.get('lockUniScaling')) { 13595 13596 // middle-top 13597 this._drawControl('mt', ctx, methodName, 13598 left + width/2, 13599 top); 13600 13601 // middle-bottom 13602 this._drawControl('mb', ctx, methodName, 13603 left + width/2, 13604 top + height); 13605 13606 // middle-right 13607 this._drawControl('mr', ctx, methodName, 13608 left + width, 13609 top + height/2); 13610 13611 // middle-left 13612 this._drawControl('ml', ctx, methodName, 13613 left, 13614 top + height/2); 13615 } 13616 13617 // middle-top-rotate 13618 if (this.hasRotatingPoint) { 13619 this._drawControl('mtr', ctx, methodName, 13620 left + width / 2, 13621 top - this.rotatingPointOffset); 13622 } 13623 13624 ctx.restore(); 13625 13626 return this; 13627 }, 13628 13629 /** 13630 * @private 13631 */ 13632 _drawControl: function(control, ctx, methodName, left, top) { 13633 if (!this.isControlVisible(control)) { 13634 return; 13635 } 13636 var size = this.cornerSize; 13637 isVML() || this.transparentCorners || ctx.clearRect(left, top, size, size); 13638 ctx[methodName](left, top, size, size); 13639 }, 13640 13641 /** 13642 * Returns true if the specified control is visible, false otherwise. 13643 * @param {String} controlName The name of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'. 13644 * @returns {Boolean} true if the specified control is visible, false otherwise 13645 */ 13646 isControlVisible: function(controlName) { 13647 return this._getControlsVisibility()[controlName]; 13648 }, 13649 13650 /** 13651 * Sets the visibility of the specified control. 13652 * @param {String} controlName The name of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'. 13653 * @param {Boolean} visible true to set the specified control visible, false otherwise 13654 * @return {fabric.Object} thisArg 13655 * @chainable 13656 */ 13657 setControlVisible: function(controlName, visible) { 13658 this._getControlsVisibility()[controlName] = visible; 13659 return this; 13660 }, 13661 13662 /** 13663 * Sets the visibility state of object controls. 13664 * @param {Object} [options] Options object 13665 * @param {Boolean} [options.bl] true to enable the bottom-left control, false to disable it 13666 * @param {Boolean} [options.br] true to enable the bottom-right control, false to disable it 13667 * @param {Boolean} [options.mb] true to enable the middle-bottom control, false to disable it 13668 * @param {Boolean} [options.ml] true to enable the middle-left control, false to disable it 13669 * @param {Boolean} [options.mr] true to enable the middle-right control, false to disable it 13670 * @param {Boolean} [options.mt] true to enable the middle-top control, false to disable it 13671 * @param {Boolean} [options.tl] true to enable the top-left control, false to disable it 13672 * @param {Boolean} [options.tr] true to enable the top-right control, false to disable it 13673 * @param {Boolean} [options.mtr] true to enable the middle-top-rotate control, false to disable it 13674 * @return {fabric.Object} thisArg 13675 * @chainable 13676 */ 13677 setControlsVisibility: function(options) { 13678 options || (options = { }); 13679 13680 for (var p in options) { 13681 this.setControlVisible(p, options[p]); 13682 } 13683 return this; 13684 }, 13685 13686 /** 13687 * Returns the instance of the control visibility set for this object. 13688 * @private 13689 * @returns {Object} 13690 */ 13691 _getControlsVisibility: function() { 13692 if (!this._controlsVisibility) { 13693 this._controlsVisibility = { 13694 tl: true, 13695 tr: true, 13696 br: true, 13697 bl: true, 13698 ml: true, 13699 mt: true, 13700 mr: true, 13701 mb: true, 13702 mtr: true 13703 }; 13704 } 13705 return this._controlsVisibility; 13706 } 13707 }); 13708 })(); 13709 13710 13711 fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { 13712 13713 /** 13714 * Animation duration (in ms) for fx* methods 13715 * @type Number 13716 * @default 13717 */ 13718 FX_DURATION: 500, 13719 13720 /** 13721 * Centers object horizontally with animation. 13722 * @param {fabric.Object} object Object to center 13723 * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties 13724 * @param {Function} [callbacks.onComplete] Invoked on completion 13725 * @param {Function} [callbacks.onChange] Invoked on every step of animation 13726 * @return {fabric.Canvas} thisArg 13727 * @chainable 13728 */ 13729 fxCenterObjectH: function (object, callbacks) { 13730 callbacks = callbacks || { }; 13731 13732 var empty = function() { }, 13733 onComplete = callbacks.onComplete || empty, 13734 onChange = callbacks.onChange || empty, 13735 _this = this; 13736 13737 fabric.util.animate({ 13738 startValue: object.get('left'), 13739 endValue: this.getCenter().left, 13740 duration: this.FX_DURATION, 13741 onChange: function(value) { 13742 object.set('left', value); 13743 _this.renderAll(); 13744 onChange(); 13745 }, 13746 onComplete: function() { 13747 object.setCoords(); 13748 onComplete(); 13749 } 13750 }); 13751 13752 return this; 13753 }, 13754 13755 /** 13756 * Centers object vertically with animation. 13757 * @param {fabric.Object} object Object to center 13758 * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties 13759 * @param {Function} [callbacks.onComplete] Invoked on completion 13760 * @param {Function} [callbacks.onChange] Invoked on every step of animation 13761 * @return {fabric.Canvas} thisArg 13762 * @chainable 13763 */ 13764 fxCenterObjectV: function (object, callbacks) { 13765 callbacks = callbacks || { }; 13766 13767 var empty = function() { }, 13768 onComplete = callbacks.onComplete || empty, 13769 onChange = callbacks.onChange || empty, 13770 _this = this; 13771 13772 fabric.util.animate({ 13773 startValue: object.get('top'), 13774 endValue: this.getCenter().top, 13775 duration: this.FX_DURATION, 13776 onChange: function(value) { 13777 object.set('top', value); 13778 _this.renderAll(); 13779 onChange(); 13780 }, 13781 onComplete: function() { 13782 object.setCoords(); 13783 onComplete(); 13784 } 13785 }); 13786 13787 return this; 13788 }, 13789 13790 /** 13791 * Same as `fabric.Canvas#remove` but animated 13792 * @param {fabric.Object} object Object to remove 13793 * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties 13794 * @param {Function} [callbacks.onComplete] Invoked on completion 13795 * @param {Function} [callbacks.onChange] Invoked on every step of animation 13796 * @return {fabric.Canvas} thisArg 13797 * @chainable 13798 */ 13799 fxRemove: function (object, callbacks) { 13800 callbacks = callbacks || { }; 13801 13802 var empty = function() { }, 13803 onComplete = callbacks.onComplete || empty, 13804 onChange = callbacks.onChange || empty, 13805 _this = this; 13806 13807 fabric.util.animate({ 13808 startValue: object.get('opacity'), 13809 endValue: 0, 13810 duration: this.FX_DURATION, 13811 onStart: function() { 13812 object.set('active', false); 13813 }, 13814 onChange: function(value) { 13815 object.set('opacity', value); 13816 _this.renderAll(); 13817 onChange(); 13818 }, 13819 onComplete: function () { 13820 _this.remove(object); 13821 onComplete(); 13822 } 13823 }); 13824 13825 return this; 13826 } 13827 }); 13828 13829 fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { 13830 /** 13831 * Animates object's properties 13832 * @param {String|Object} property Property to animate (if string) or properties to animate (if object) 13833 * @param {Number|Object} value Value to animate property to (if string was given first) or options object 13834 * @return {fabric.Object} thisArg 13835 * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#animation} 13836 * @chainable 13837 * 13838 * As object — multiple properties 13839 * 13840 * object.animate({ left: ..., top: ... }); 13841 * object.animate({ left: ..., top: ... }, { duration: ... }); 13842 * 13843 * As string — one property 13844 * 13845 * object.animate('left', ...); 13846 * object.animate('left', { duration: ... }); 13847 * 13848 */ 13849 animate: function() { 13850 if (arguments[0] && typeof arguments[0] === 'object') { 13851 var propsToAnimate = [ ], prop, skipCallbacks; 13852 for (prop in arguments[0]) { 13853 propsToAnimate.push(prop); 13854 } 13855 for (var i = 0, len = propsToAnimate.length; i < len; i++) { 13856 prop = propsToAnimate[i]; 13857 skipCallbacks = i !== len - 1; 13858 this._animate(prop, arguments[0][prop], arguments[1], skipCallbacks); 13859 } 13860 } 13861 else { 13862 this._animate.apply(this, arguments); 13863 } 13864 return this; 13865 }, 13866 13867 /** 13868 * @private 13869 * @param {String} property Property to animate 13870 * @param {String} to Value to animate to 13871 * @param {Object} [options] Options object 13872 * @param {Boolean} [skipCallbacks] When true, callbacks like onchange and oncomplete are not invoked 13873 */ 13874 _animate: function(property, to, options, skipCallbacks) { 13875 var _this = this, propPair; 13876 13877 to = to.toString(); 13878 13879 if (!options) { 13880 options = { }; 13881 } 13882 else { 13883 options = fabric.util.object.clone(options); 13884 } 13885 13886 if (~property.indexOf('.')) { 13887 propPair = property.split('.'); 13888 } 13889 13890 var currentValue = propPair 13891 ? this.get(propPair[0])[propPair[1]] 13892 : this.get(property); 13893 13894 if (!('from' in options)) { 13895 options.from = currentValue; 13896 } 13897 13898 if (~to.indexOf('=')) { 13899 to = currentValue + parseFloat(to.replace('=', '')); 13900 } 13901 else { 13902 to = parseFloat(to); 13903 } 13904 13905 fabric.util.animate({ 13906 startValue: options.from, 13907 endValue: to, 13908 byValue: options.by, 13909 easing: options.easing, 13910 duration: options.duration, 13911 abort: options.abort && function() { 13912 return options.abort.call(_this); 13913 }, 13914 onChange: function(value) { 13915 if (propPair) { 13916 _this[propPair[0]][propPair[1]] = value; 13917 } 13918 else { 13919 _this.set(property, value); 13920 } 13921 if (skipCallbacks) { 13922 return; 13923 } 13924 options.onChange && options.onChange(); 13925 }, 13926 onComplete: function() { 13927 if (skipCallbacks) { 13928 return; 13929 } 13930 13931 _this.setCoords(); 13932 options.onComplete && options.onComplete(); 13933 } 13934 }); 13935 } 13936 }); 13937 13938 13939 (function(global) { 13940 13941 'use strict'; 13942 13943 var fabric = global.fabric || (global.fabric = { }), 13944 extend = fabric.util.object.extend, 13945 coordProps = { x1: 1, x2: 1, y1: 1, y2: 1 }, 13946 supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); 13947 13948 if (fabric.Line) { 13949 fabric.warn('fabric.Line is already defined'); 13950 return; 13951 } 13952 13953 /** 13954 * Line class 13955 * @class fabric.Line 13956 * @extends fabric.Object 13957 * @see {@link fabric.Line#initialize} for constructor definition 13958 */ 13959 fabric.Line = fabric.util.createClass(fabric.Object, /** @lends fabric.Line.prototype */ { 13960 13961 /** 13962 * Type of an object 13963 * @type String 13964 * @default 13965 */ 13966 type: 'line', 13967 13968 /** 13969 * x value or first line edge 13970 * @type Number 13971 * @default 13972 */ 13973 x1: 0, 13974 13975 /** 13976 * y value or first line edge 13977 * @type Number 13978 * @default 13979 */ 13980 y1: 0, 13981 13982 /** 13983 * x value or second line edge 13984 * @type Number 13985 * @default 13986 */ 13987 x2: 0, 13988 13989 /** 13990 * y value or second line edge 13991 * @type Number 13992 * @default 13993 */ 13994 y2: 0, 13995 13996 /** 13997 * Constructor 13998 * @param {Array} [points] Array of points 13999 * @param {Object} [options] Options object 14000 * @return {fabric.Line} thisArg 14001 */ 14002 initialize: function(points, options) { 14003 options = options || { }; 14004 14005 if (!points) { 14006 points = [0, 0, 0, 0]; 14007 } 14008 14009 this.callSuper('initialize', options); 14010 14011 this.set('x1', points[0]); 14012 this.set('y1', points[1]); 14013 this.set('x2', points[2]); 14014 this.set('y2', points[3]); 14015 14016 this._setWidthHeight(options); 14017 }, 14018 14019 /** 14020 * @private 14021 * @param {Object} [options] Options 14022 */ 14023 _setWidthHeight: function(options) { 14024 options || (options = { }); 14025 14026 this.width = Math.abs(this.x2 - this.x1); 14027 this.height = Math.abs(this.y2 - this.y1); 14028 14029 this.left = 'left' in options 14030 ? options.left 14031 : this._getLeftToOriginX(); 14032 14033 this.top = 'top' in options 14034 ? options.top 14035 : this._getTopToOriginY(); 14036 }, 14037 14038 /** 14039 * @private 14040 * @param {String} key 14041 * @param {Any} value 14042 */ 14043 _set: function(key, value) { 14044 this.callSuper('_set', key, value); 14045 if (typeof coordProps[key] !== 'undefined') { 14046 this._setWidthHeight(); 14047 } 14048 return this; 14049 }, 14050 14051 /** 14052 * @private 14053 * @return {Number} leftToOriginX Distance from left edge of canvas to originX of Line. 14054 */ 14055 _getLeftToOriginX: makeEdgeToOriginGetter( 14056 { // property names 14057 origin: 'originX', 14058 axis1: 'x1', 14059 axis2: 'x2', 14060 dimension: 'width' 14061 }, 14062 { // possible values of origin 14063 nearest: 'left', 14064 center: 'center', 14065 farthest: 'right' 14066 } 14067 ), 14068 14069 /** 14070 * @private 14071 * @return {Number} topToOriginY Distance from top edge of canvas to originY of Line. 14072 */ 14073 _getTopToOriginY: makeEdgeToOriginGetter( 14074 { // property names 14075 origin: 'originY', 14076 axis1: 'y1', 14077 axis2: 'y2', 14078 dimension: 'height' 14079 }, 14080 { // possible values of origin 14081 nearest: 'top', 14082 center: 'center', 14083 farthest: 'bottom' 14084 } 14085 ), 14086 14087 /** 14088 * @private 14089 * @param {CanvasRenderingContext2D} ctx Context to render on 14090 */ 14091 _render: function(ctx, noTransform) { 14092 ctx.beginPath(); 14093 14094 if (noTransform) { 14095 // Line coords are distances from left-top of canvas to origin of line. 14096 // To render line in a path-group, we need to translate them to 14097 // distances from center of path-group to center of line. 14098 var cp = this.getCenterPoint(); 14099 ctx.translate( 14100 cp.x - this.strokeWidth / 2, 14101 cp.y - this.strokeWidth / 2 14102 ); 14103 } 14104 14105 if (!this.strokeDashArray || this.strokeDashArray && supportsLineDash) { 14106 // move from center (of virtual box) to its left/top corner 14107 // we can't assume x1, y1 is top left and x2, y2 is bottom right 14108 var p = this.calcLinePoints(); 14109 ctx.moveTo(p.x1, p.y1); 14110 ctx.lineTo(p.x2, p.y2); 14111 } 14112 14113 ctx.lineWidth = this.strokeWidth; 14114 14115 // TODO: test this 14116 // make sure setting "fill" changes color of a line 14117 // (by copying fillStyle to strokeStyle, since line is stroked, not filled) 14118 var origStrokeStyle = ctx.strokeStyle; 14119 ctx.strokeStyle = this.stroke || ctx.fillStyle; 14120 this.stroke && this._renderStroke(ctx); 14121 ctx.strokeStyle = origStrokeStyle; 14122 }, 14123 14124 /** 14125 * @private 14126 * @param {CanvasRenderingContext2D} ctx Context to render on 14127 */ 14128 _renderDashedStroke: function(ctx) { 14129 var p = this.calcLinePoints(); 14130 14131 ctx.beginPath(); 14132 fabric.util.drawDashedLine(ctx, p.x1, p.y1, p.x2, p.y2, this.strokeDashArray); 14133 ctx.closePath(); 14134 }, 14135 14136 /** 14137 * Returns object representation of an instance 14138 * @methd toObject 14139 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 14140 * @return {Object} object representation of an instance 14141 */ 14142 toObject: function(propertiesToInclude) { 14143 return extend(this.callSuper('toObject', propertiesToInclude), this.calcLinePoints()); 14144 }, 14145 14146 /** 14147 * Recalculates line points given width and height 14148 * @private 14149 */ 14150 calcLinePoints: function() { 14151 var xMult = this.x1 <= this.x2 ? -1 : 1, 14152 yMult = this.y1 <= this.y2 ? -1 : 1, 14153 x1 = (xMult * this.width * 0.5), 14154 y1 = (yMult * this.height * 0.5), 14155 x2 = (xMult * this.width * -0.5), 14156 y2 = (yMult * this.height * -0.5); 14157 14158 return { 14159 x1: x1, 14160 x2: x2, 14161 y1: y1, 14162 y2: y2 14163 }; 14164 }, 14165 14166 /* _TO_SVG_START_ */ 14167 /** 14168 * Returns SVG representation of an instance 14169 * @param {Function} [reviver] Method for further parsing of svg representation. 14170 * @return {String} svg representation of an instance 14171 */ 14172 toSVG: function(reviver) { 14173 var markup = this._createBaseSVGMarkup(), 14174 p = { x1: this.x1, x2: this.x2, y1: this.y1, y2: this.y2 }; 14175 14176 if (!(this.group && this.group.type === 'path-group')) { 14177 p = this.calcLinePoints(); 14178 } 14179 markup.push( 14180 '<line ', 14181 'x1="', p.x1, 14182 '" y1="', p.y1, 14183 '" x2="', p.x2, 14184 '" y2="', p.y2, 14185 '" style="', this.getSvgStyles(), 14186 '" transform="', this.getSvgTransform(), 14187 this.getSvgTransformMatrix(), 14188 '"/>\n' 14189 ); 14190 14191 return reviver ? reviver(markup.join('')) : markup.join(''); 14192 }, 14193 /* _TO_SVG_END_ */ 14194 14195 /** 14196 * Returns complexity of an instance 14197 * @return {Number} complexity 14198 */ 14199 complexity: function() { 14200 return 1; 14201 } 14202 }); 14203 14204 /* _FROM_SVG_START_ */ 14205 /** 14206 * List of attribute names to account for when parsing SVG element (used by {@link fabric.Line.fromElement}) 14207 * @static 14208 * @memberOf fabric.Line 14209 * @see http://www.w3.org/TR/SVG/shapes.html#LineElement 14210 */ 14211 fabric.Line.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x1 y1 x2 y2'.split(' ')); 14212 14213 /** 14214 * Returns fabric.Line instance from an SVG element 14215 * @static 14216 * @memberOf fabric.Line 14217 * @param {SVGElement} element Element to parse 14218 * @param {Object} [options] Options object 14219 * @return {fabric.Line} instance of fabric.Line 14220 */ 14221 fabric.Line.fromElement = function(element, options) { 14222 var parsedAttributes = fabric.parseAttributes(element, fabric.Line.ATTRIBUTE_NAMES), 14223 points = [ 14224 parsedAttributes.x1 || 0, 14225 parsedAttributes.y1 || 0, 14226 parsedAttributes.x2 || 0, 14227 parsedAttributes.y2 || 0 14228 ]; 14229 return new fabric.Line(points, extend(parsedAttributes, options)); 14230 }; 14231 /* _FROM_SVG_END_ */ 14232 14233 /** 14234 * Returns fabric.Line instance from an object representation 14235 * @static 14236 * @memberOf fabric.Line 14237 * @param {Object} object Object to create an instance from 14238 * @return {fabric.Line} instance of fabric.Line 14239 */ 14240 fabric.Line.fromObject = function(object) { 14241 var points = [object.x1, object.y1, object.x2, object.y2]; 14242 return new fabric.Line(points, object); 14243 }; 14244 14245 /** 14246 * Produces a function that calculates distance from canvas edge to Line origin. 14247 */ 14248 function makeEdgeToOriginGetter(propertyNames, originValues) { 14249 var origin = propertyNames.origin, 14250 axis1 = propertyNames.axis1, 14251 axis2 = propertyNames.axis2, 14252 dimension = propertyNames.dimension, 14253 nearest = originValues.nearest, 14254 center = originValues.center, 14255 farthest = originValues.farthest; 14256 14257 return function() { 14258 switch (this.get(origin)) { 14259 case nearest: 14260 return Math.min(this.get(axis1), this.get(axis2)); 14261 case center: 14262 return Math.min(this.get(axis1), this.get(axis2)) + (0.5 * this.get(dimension)); 14263 case farthest: 14264 return Math.max(this.get(axis1), this.get(axis2)); 14265 } 14266 }; 14267 14268 } 14269 14270 })(typeof exports !== 'undefined' ? exports : this); 14271 14272 14273 (function(global) { 14274 14275 'use strict'; 14276 14277 var fabric = global.fabric || (global.fabric = { }), 14278 pi = Math.PI, 14279 extend = fabric.util.object.extend; 14280 14281 if (fabric.Circle) { 14282 fabric.warn('fabric.Circle is already defined.'); 14283 return; 14284 } 14285 14286 /** 14287 * Circle class 14288 * @class fabric.Circle 14289 * @extends fabric.Object 14290 * @see {@link fabric.Circle#initialize} for constructor definition 14291 */ 14292 fabric.Circle = fabric.util.createClass(fabric.Object, /** @lends fabric.Circle.prototype */ { 14293 14294 /** 14295 * Type of an object 14296 * @type String 14297 * @default 14298 */ 14299 type: 'circle', 14300 14301 /** 14302 * Radius of this circle 14303 * @type Number 14304 * @default 14305 */ 14306 radius: 0, 14307 14308 /** 14309 * Start angle of the circle, moving clockwise 14310 * @type Number 14311 * @default 0 14312 */ 14313 startAngle: 0, 14314 14315 /** 14316 * End angle of the circle 14317 * @type Number 14318 * @default 2Pi 14319 */ 14320 endAngle: pi * 2, 14321 14322 /** 14323 * Constructor 14324 * @param {Object} [options] Options object 14325 * @return {fabric.Circle} thisArg 14326 */ 14327 initialize: function(options) { 14328 options = options || { }; 14329 14330 this.callSuper('initialize', options); 14331 this.set('radius', options.radius || 0); 14332 14333 this.startAngle = options.startAngle || this.startAngle; 14334 this.endAngle = options.endAngle || this.endAngle; 14335 }, 14336 14337 /** 14338 * @private 14339 * @param {String} key 14340 * @param {Any} value 14341 * @return {fabric.Circle} thisArg 14342 */ 14343 _set: function(key, value) { 14344 this.callSuper('_set', key, value); 14345 14346 if (key === 'radius') { 14347 this.setRadius(value); 14348 } 14349 14350 return this; 14351 }, 14352 14353 /** 14354 * Returns object representation of an instance 14355 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 14356 * @return {Object} object representation of an instance 14357 */ 14358 toObject: function(propertiesToInclude) { 14359 return extend(this.callSuper('toObject', propertiesToInclude), { 14360 radius: this.get('radius'), 14361 startAngle: this.startAngle, 14362 endAngle: this.endAngle 14363 }); 14364 }, 14365 14366 /* _TO_SVG_START_ */ 14367 /** 14368 * Returns svg representation of an instance 14369 * @param {Function} [reviver] Method for further parsing of svg representation. 14370 * @return {String} svg representation of an instance 14371 */ 14372 toSVG: function(reviver) { 14373 var markup = this._createBaseSVGMarkup(), x = 0, y = 0, 14374 angle = (this.endAngle - this.startAngle) % ( 2 * pi); 14375 14376 if (angle === 0) { 14377 if (this.group && this.group.type === 'path-group') { 14378 x = this.left + this.radius; 14379 y = this.top + this.radius; 14380 } 14381 markup.push( 14382 '<circle ', 14383 'cx="' + x + '" cy="' + y + '" ', 14384 'r="', this.radius, 14385 '" style="', this.getSvgStyles(), 14386 '" transform="', this.getSvgTransform(), 14387 ' ', this.getSvgTransformMatrix(), 14388 '"/>\n' 14389 ); 14390 } 14391 else { 14392 var startX = Math.cos(this.startAngle) * this.radius, 14393 startY = Math.sin(this.startAngle) * this.radius, 14394 endX = Math.cos(this.endAngle) * this.radius, 14395 endY = Math.sin(this.endAngle) * this.radius, 14396 largeFlag = angle > pi ? '1' : '0'; 14397 14398 markup.push( 14399 '<path d="M ' + startX + ' ' + startY, 14400 ' A ' + this.radius + ' ' + this.radius, 14401 ' 0 ', + largeFlag + ' 1', ' ' + endX + ' ' + endY, 14402 '" style="', this.getSvgStyles(), 14403 '" transform="', this.getSvgTransform(), 14404 ' ', this.getSvgTransformMatrix(), 14405 '"/>\n' 14406 ); 14407 } 14408 14409 return reviver ? reviver(markup.join('')) : markup.join(''); 14410 }, 14411 /* _TO_SVG_END_ */ 14412 14413 /** 14414 * @private 14415 * @param {CanvasRenderingContext2D} ctx context to render on 14416 * @param {Boolean} [noTransform] When true, context is not transformed 14417 */ 14418 _render: function(ctx, noTransform) { 14419 ctx.beginPath(); 14420 ctx.arc(noTransform ? this.left + this.radius : 0, 14421 noTransform ? this.top + this.radius : 0, 14422 this.radius, 14423 this.startAngle, 14424 this.endAngle, false); 14425 this._renderFill(ctx); 14426 this._renderStroke(ctx); 14427 }, 14428 14429 /** 14430 * Returns horizontal radius of an object (according to how an object is scaled) 14431 * @return {Number} 14432 */ 14433 getRadiusX: function() { 14434 return this.get('radius') * this.get('scaleX'); 14435 }, 14436 14437 /** 14438 * Returns vertical radius of an object (according to how an object is scaled) 14439 * @return {Number} 14440 */ 14441 getRadiusY: function() { 14442 return this.get('radius') * this.get('scaleY'); 14443 }, 14444 14445 /** 14446 * Sets radius of an object (and updates width accordingly) 14447 * @return {fabric.Circle} thisArg 14448 */ 14449 setRadius: function(value) { 14450 this.radius = value; 14451 return this.set('width', value * 2).set('height', value * 2); 14452 }, 14453 14454 /** 14455 * Returns complexity of an instance 14456 * @return {Number} complexity of this instance 14457 */ 14458 complexity: function() { 14459 return 1; 14460 } 14461 }); 14462 14463 /* _FROM_SVG_START_ */ 14464 /** 14465 * List of attribute names to account for when parsing SVG element (used by {@link fabric.Circle.fromElement}) 14466 * @static 14467 * @memberOf fabric.Circle 14468 * @see: http://www.w3.org/TR/SVG/shapes.html#CircleElement 14469 */ 14470 fabric.Circle.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy r'.split(' ')); 14471 14472 /** 14473 * Returns {@link fabric.Circle} instance from an SVG element 14474 * @static 14475 * @memberOf fabric.Circle 14476 * @param {SVGElement} element Element to parse 14477 * @param {Object} [options] Options object 14478 * @throws {Error} If value of `r` attribute is missing or invalid 14479 * @return {fabric.Circle} Instance of fabric.Circle 14480 */ 14481 fabric.Circle.fromElement = function(element, options) { 14482 options || (options = { }); 14483 14484 var parsedAttributes = fabric.parseAttributes(element, fabric.Circle.ATTRIBUTE_NAMES); 14485 14486 if (!isValidRadius(parsedAttributes)) { 14487 throw new Error('value of `r` attribute is required and can not be negative'); 14488 } 14489 14490 parsedAttributes.left = parsedAttributes.left || 0; 14491 parsedAttributes.top = parsedAttributes.top || 0; 14492 14493 var obj = new fabric.Circle(extend(parsedAttributes, options)); 14494 14495 obj.left -= obj.radius; 14496 obj.top -= obj.radius; 14497 return obj; 14498 }; 14499 14500 /** 14501 * @private 14502 */ 14503 function isValidRadius(attributes) { 14504 return (('radius' in attributes) && (attributes.radius >= 0)); 14505 } 14506 /* _FROM_SVG_END_ */ 14507 14508 /** 14509 * Returns {@link fabric.Circle} instance from an object representation 14510 * @static 14511 * @memberOf fabric.Circle 14512 * @param {Object} object Object to create an instance from 14513 * @return {Object} Instance of fabric.Circle 14514 */ 14515 fabric.Circle.fromObject = function(object) { 14516 return new fabric.Circle(object); 14517 }; 14518 14519 })(typeof exports !== 'undefined' ? exports : this); 14520 14521 14522 (function(global) { 14523 14524 'use strict'; 14525 14526 var fabric = global.fabric || (global.fabric = { }); 14527 14528 if (fabric.Triangle) { 14529 fabric.warn('fabric.Triangle is already defined'); 14530 return; 14531 } 14532 14533 /** 14534 * Triangle class 14535 * @class fabric.Triangle 14536 * @extends fabric.Object 14537 * @return {fabric.Triangle} thisArg 14538 * @see {@link fabric.Triangle#initialize} for constructor definition 14539 */ 14540 fabric.Triangle = fabric.util.createClass(fabric.Object, /** @lends fabric.Triangle.prototype */ { 14541 14542 /** 14543 * Type of an object 14544 * @type String 14545 * @default 14546 */ 14547 type: 'triangle', 14548 14549 /** 14550 * Constructor 14551 * @param {Object} [options] Options object 14552 * @return {Object} thisArg 14553 */ 14554 initialize: function(options) { 14555 options = options || { }; 14556 14557 this.callSuper('initialize', options); 14558 14559 this.set('width', options.width || 100) 14560 .set('height', options.height || 100); 14561 }, 14562 14563 /** 14564 * @private 14565 * @param {CanvasRenderingContext2D} ctx Context to render on 14566 */ 14567 _render: function(ctx) { 14568 var widthBy2 = this.width / 2, 14569 heightBy2 = this.height / 2; 14570 14571 ctx.beginPath(); 14572 ctx.moveTo(-widthBy2, heightBy2); 14573 ctx.lineTo(0, -heightBy2); 14574 ctx.lineTo(widthBy2, heightBy2); 14575 ctx.closePath(); 14576 14577 this._renderFill(ctx); 14578 this._renderStroke(ctx); 14579 }, 14580 14581 /** 14582 * @private 14583 * @param {CanvasRenderingContext2D} ctx Context to render on 14584 */ 14585 _renderDashedStroke: function(ctx) { 14586 var widthBy2 = this.width / 2, 14587 heightBy2 = this.height / 2; 14588 14589 ctx.beginPath(); 14590 fabric.util.drawDashedLine(ctx, -widthBy2, heightBy2, 0, -heightBy2, this.strokeDashArray); 14591 fabric.util.drawDashedLine(ctx, 0, -heightBy2, widthBy2, heightBy2, this.strokeDashArray); 14592 fabric.util.drawDashedLine(ctx, widthBy2, heightBy2, -widthBy2, heightBy2, this.strokeDashArray); 14593 ctx.closePath(); 14594 }, 14595 14596 /* _TO_SVG_START_ */ 14597 /** 14598 * Returns SVG representation of an instance 14599 * @param {Function} [reviver] Method for further parsing of svg representation. 14600 * @return {String} svg representation of an instance 14601 */ 14602 toSVG: function(reviver) { 14603 var markup = this._createBaseSVGMarkup(), 14604 widthBy2 = this.width / 2, 14605 heightBy2 = this.height / 2, 14606 points = [ 14607 -widthBy2 + ' ' + heightBy2, 14608 '0 ' + -heightBy2, 14609 widthBy2 + ' ' + heightBy2 14610 ] 14611 .join(','); 14612 14613 markup.push( 14614 '<polygon ', 14615 'points="', points, 14616 '" style="', this.getSvgStyles(), 14617 '" transform="', this.getSvgTransform(), 14618 '"/>' 14619 ); 14620 14621 return reviver ? reviver(markup.join('')) : markup.join(''); 14622 }, 14623 /* _TO_SVG_END_ */ 14624 14625 /** 14626 * Returns complexity of an instance 14627 * @return {Number} complexity of this instance 14628 */ 14629 complexity: function() { 14630 return 1; 14631 } 14632 }); 14633 14634 /** 14635 * Returns fabric.Triangle instance from an object representation 14636 * @static 14637 * @memberOf fabric.Triangle 14638 * @param {Object} object Object to create an instance from 14639 * @return {Object} instance of Canvas.Triangle 14640 */ 14641 fabric.Triangle.fromObject = function(object) { 14642 return new fabric.Triangle(object); 14643 }; 14644 14645 })(typeof exports !== 'undefined' ? exports : this); 14646 14647 14648 (function(global) { 14649 14650 'use strict'; 14651 14652 var fabric = global.fabric || (global.fabric = { }), 14653 piBy2 = Math.PI * 2, 14654 extend = fabric.util.object.extend; 14655 14656 if (fabric.Ellipse) { 14657 fabric.warn('fabric.Ellipse is already defined.'); 14658 return; 14659 } 14660 14661 /** 14662 * Ellipse class 14663 * @class fabric.Ellipse 14664 * @extends fabric.Object 14665 * @return {fabric.Ellipse} thisArg 14666 * @see {@link fabric.Ellipse#initialize} for constructor definition 14667 */ 14668 fabric.Ellipse = fabric.util.createClass(fabric.Object, /** @lends fabric.Ellipse.prototype */ { 14669 14670 /** 14671 * Type of an object 14672 * @type String 14673 * @default 14674 */ 14675 type: 'ellipse', 14676 14677 /** 14678 * Horizontal radius 14679 * @type Number 14680 * @default 14681 */ 14682 rx: 0, 14683 14684 /** 14685 * Vertical radius 14686 * @type Number 14687 * @default 14688 */ 14689 ry: 0, 14690 14691 /** 14692 * Constructor 14693 * @param {Object} [options] Options object 14694 * @return {fabric.Ellipse} thisArg 14695 */ 14696 initialize: function(options) { 14697 options = options || { }; 14698 14699 this.callSuper('initialize', options); 14700 14701 this.set('rx', options.rx || 0); 14702 this.set('ry', options.ry || 0); 14703 }, 14704 14705 /** 14706 * @private 14707 * @param {String} key 14708 * @param {Any} value 14709 * @return {fabric.Ellipse} thisArg 14710 */ 14711 _set: function(key, value) { 14712 this.callSuper('_set', key, value); 14713 switch (key) { 14714 14715 case 'rx': 14716 this.rx = value; 14717 this.set('width', value * 2); 14718 break; 14719 14720 case 'ry': 14721 this.ry = value; 14722 this.set('height', value * 2); 14723 break; 14724 14725 } 14726 return this; 14727 }, 14728 14729 /** 14730 * Returns horizontal radius of an object (according to how an object is scaled) 14731 * @return {Number} 14732 */ 14733 getRx: function() { 14734 return this.get('rx') * this.get('scaleX'); 14735 }, 14736 14737 /** 14738 * Returns Vertical radius of an object (according to how an object is scaled) 14739 * @return {Number} 14740 */ 14741 getRy: function() { 14742 return this.get('ry') * this.get('scaleY'); 14743 }, 14744 14745 /** 14746 * Returns object representation of an instance 14747 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 14748 * @return {Object} object representation of an instance 14749 */ 14750 toObject: function(propertiesToInclude) { 14751 return extend(this.callSuper('toObject', propertiesToInclude), { 14752 rx: this.get('rx'), 14753 ry: this.get('ry') 14754 }); 14755 }, 14756 14757 /* _TO_SVG_START_ */ 14758 /** 14759 * Returns svg representation of an instance 14760 * @param {Function} [reviver] Method for further parsing of svg representation. 14761 * @return {String} svg representation of an instance 14762 */ 14763 toSVG: function(reviver) { 14764 var markup = this._createBaseSVGMarkup(), x = 0, y = 0; 14765 if (this.group && this.group.type === 'path-group') { 14766 x = this.left + this.rx; 14767 y = this.top + this.ry; 14768 } 14769 markup.push( 14770 '<ellipse ', 14771 'cx="', x, '" cy="', y, '" ', 14772 'rx="', this.rx, 14773 '" ry="', this.ry, 14774 '" style="', this.getSvgStyles(), 14775 '" transform="', this.getSvgTransform(), 14776 this.getSvgTransformMatrix(), 14777 '"/>\n' 14778 ); 14779 14780 return reviver ? reviver(markup.join('')) : markup.join(''); 14781 }, 14782 /* _TO_SVG_END_ */ 14783 14784 /** 14785 * @private 14786 * @param {CanvasRenderingContext2D} ctx context to render on 14787 * @param {Boolean} [noTransform] When true, context is not transformed 14788 */ 14789 _render: function(ctx, noTransform) { 14790 ctx.beginPath(); 14791 ctx.save(); 14792 ctx.transform(1, 0, 0, this.ry/this.rx, 0, 0); 14793 ctx.arc( 14794 noTransform ? this.left + this.rx : 0, 14795 noTransform ? (this.top + this.ry) * this.rx/this.ry : 0, 14796 this.rx, 14797 0, 14798 piBy2, 14799 false); 14800 ctx.restore(); 14801 this._renderFill(ctx); 14802 this._renderStroke(ctx); 14803 }, 14804 14805 /** 14806 * Returns complexity of an instance 14807 * @return {Number} complexity 14808 */ 14809 complexity: function() { 14810 return 1; 14811 } 14812 }); 14813 14814 /* _FROM_SVG_START_ */ 14815 /** 14816 * List of attribute names to account for when parsing SVG element (used by {@link fabric.Ellipse.fromElement}) 14817 * @static 14818 * @memberOf fabric.Ellipse 14819 * @see http://www.w3.org/TR/SVG/shapes.html#EllipseElement 14820 */ 14821 fabric.Ellipse.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy rx ry'.split(' ')); 14822 14823 /** 14824 * Returns {@link fabric.Ellipse} instance from an SVG element 14825 * @static 14826 * @memberOf fabric.Ellipse 14827 * @param {SVGElement} element Element to parse 14828 * @param {Object} [options] Options object 14829 * @return {fabric.Ellipse} 14830 */ 14831 fabric.Ellipse.fromElement = function(element, options) { 14832 options || (options = { }); 14833 14834 var parsedAttributes = fabric.parseAttributes(element, fabric.Ellipse.ATTRIBUTE_NAMES); 14835 14836 parsedAttributes.left = parsedAttributes.left || 0; 14837 parsedAttributes.top = parsedAttributes.top || 0; 14838 14839 var ellipse = new fabric.Ellipse(extend(parsedAttributes, options)); 14840 14841 ellipse.top -= ellipse.ry; 14842 ellipse.left -= ellipse.rx; 14843 return ellipse; 14844 }; 14845 /* _FROM_SVG_END_ */ 14846 14847 /** 14848 * Returns {@link fabric.Ellipse} instance from an object representation 14849 * @static 14850 * @memberOf fabric.Ellipse 14851 * @param {Object} object Object to create an instance from 14852 * @return {fabric.Ellipse} 14853 */ 14854 fabric.Ellipse.fromObject = function(object) { 14855 return new fabric.Ellipse(object); 14856 }; 14857 14858 })(typeof exports !== 'undefined' ? exports : this); 14859 14860 14861 (function(global) { 14862 14863 'use strict'; 14864 14865 var fabric = global.fabric || (global.fabric = { }), 14866 extend = fabric.util.object.extend; 14867 14868 if (fabric.Rect) { 14869 fabric.warn('fabric.Rect is already defined'); 14870 return; 14871 } 14872 14873 var stateProperties = fabric.Object.prototype.stateProperties.concat(); 14874 stateProperties.push('rx', 'ry', 'x', 'y'); 14875 14876 /** 14877 * Rectangle class 14878 * @class fabric.Rect 14879 * @extends fabric.Object 14880 * @return {fabric.Rect} thisArg 14881 * @see {@link fabric.Rect#initialize} for constructor definition 14882 */ 14883 fabric.Rect = fabric.util.createClass(fabric.Object, /** @lends fabric.Rect.prototype */ { 14884 14885 /** 14886 * List of properties to consider when checking if state of an object is changed ({@link fabric.Object#hasStateChanged}) 14887 * as well as for history (undo/redo) purposes 14888 * @type Array 14889 */ 14890 stateProperties: stateProperties, 14891 14892 /** 14893 * Type of an object 14894 * @type String 14895 * @default 14896 */ 14897 type: 'rect', 14898 14899 /** 14900 * Horizontal border radius 14901 * @type Number 14902 * @default 14903 */ 14904 rx: 0, 14905 14906 /** 14907 * Vertical border radius 14908 * @type Number 14909 * @default 14910 */ 14911 ry: 0, 14912 14913 /** 14914 * Used to specify dash pattern for stroke on this object 14915 * @type Array 14916 */ 14917 strokeDashArray: null, 14918 14919 /** 14920 * Constructor 14921 * @param {Object} [options] Options object 14922 * @return {Object} thisArg 14923 */ 14924 initialize: function(options) { 14925 options = options || { }; 14926 14927 this.callSuper('initialize', options); 14928 this._initRxRy(); 14929 14930 }, 14931 14932 /** 14933 * Initializes rx/ry attributes 14934 * @private 14935 */ 14936 _initRxRy: function() { 14937 if (this.rx && !this.ry) { 14938 this.ry = this.rx; 14939 } 14940 else if (this.ry && !this.rx) { 14941 this.rx = this.ry; 14942 } 14943 }, 14944 14945 /** 14946 * @private 14947 * @param {CanvasRenderingContext2D} ctx Context to render on 14948 */ 14949 _render: function(ctx, noTransform) { 14950 14951 // optimize 1x1 case (used in spray brush) 14952 if (this.width === 1 && this.height === 1) { 14953 ctx.fillRect(-0.5, -0.5, 1, 1); 14954 return; 14955 } 14956 14957 var rx = this.rx ? Math.min(this.rx, this.width / 2) : 0, 14958 ry = this.ry ? Math.min(this.ry, this.height / 2) : 0, 14959 w = this.width, 14960 h = this.height, 14961 x = noTransform ? this.left : -this.width / 2, 14962 y = noTransform ? this.top : -this.height / 2, 14963 isRounded = rx !== 0 || ry !== 0, 14964 k = 1 - 0.5522847498 /* "magic number" for bezier approximations of arcs (http://itc.ktu.lt/itc354/Riskus354.pdf) */; 14965 14966 ctx.beginPath(); 14967 14968 ctx.moveTo(x + rx, y); 14969 14970 ctx.lineTo(x + w - rx, y); 14971 isRounded && ctx.bezierCurveTo(x + w - k * rx, y, x + w, y + k * ry, x + w, y + ry); 14972 14973 ctx.lineTo(x + w, y + h - ry); 14974 isRounded && ctx.bezierCurveTo(x + w, y + h - k * ry, x + w - k * rx, y + h, x + w - rx, y + h); 14975 14976 ctx.lineTo(x + rx, y + h); 14977 isRounded && ctx.bezierCurveTo(x + k * rx, y + h, x, y + h - k * ry, x, y + h - ry); 14978 14979 ctx.lineTo(x, y + ry); 14980 isRounded && ctx.bezierCurveTo(x, y + k * ry, x + k * rx, y, x + rx, y); 14981 14982 ctx.closePath(); 14983 14984 this._renderFill(ctx); 14985 this._renderStroke(ctx); 14986 }, 14987 14988 /** 14989 * @private 14990 * @param {CanvasRenderingContext2D} ctx Context to render on 14991 */ 14992 _renderDashedStroke: function(ctx) { 14993 var x = -this.width / 2, 14994 y = -this.height / 2, 14995 w = this.width, 14996 h = this.height; 14997 14998 ctx.beginPath(); 14999 fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray); 15000 fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray); 15001 fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray); 15002 fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray); 15003 ctx.closePath(); 15004 }, 15005 15006 /** 15007 * Returns object representation of an instance 15008 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 15009 * @return {Object} object representation of an instance 15010 */ 15011 toObject: function(propertiesToInclude) { 15012 var object = extend(this.callSuper('toObject', propertiesToInclude), { 15013 rx: this.get('rx') || 0, 15014 ry: this.get('ry') || 0 15015 }); 15016 if (!this.includeDefaultValues) { 15017 this._removeDefaultValues(object); 15018 } 15019 return object; 15020 }, 15021 15022 /* _TO_SVG_START_ */ 15023 /** 15024 * Returns svg representation of an instance 15025 * @param {Function} [reviver] Method for further parsing of svg representation. 15026 * @return {String} svg representation of an instance 15027 */ 15028 toSVG: function(reviver) { 15029 var markup = this._createBaseSVGMarkup(), x = this.left, y = this.top; 15030 if (!(this.group && this.group.type === 'path-group')) { 15031 x = -this.width / 2; 15032 y = -this.height / 2; 15033 } 15034 markup.push( 15035 '<rect ', 15036 'x="', x, '" y="', y, 15037 '" rx="', this.get('rx'), '" ry="', this.get('ry'), 15038 '" width="', this.width, '" height="', this.height, 15039 '" style="', this.getSvgStyles(), 15040 '" transform="', this.getSvgTransform(), 15041 this.getSvgTransformMatrix(), 15042 '"/>\n'); 15043 15044 return reviver ? reviver(markup.join('')) : markup.join(''); 15045 }, 15046 /* _TO_SVG_END_ */ 15047 15048 /** 15049 * Returns complexity of an instance 15050 * @return {Number} complexity 15051 */ 15052 complexity: function() { 15053 return 1; 15054 } 15055 }); 15056 15057 /* _FROM_SVG_START_ */ 15058 /** 15059 * List of attribute names to account for when parsing SVG element (used by `fabric.Rect.fromElement`) 15060 * @static 15061 * @memberOf fabric.Rect 15062 * @see: http://www.w3.org/TR/SVG/shapes.html#RectElement 15063 */ 15064 fabric.Rect.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x y rx ry width height'.split(' ')); 15065 15066 /** 15067 * Returns {@link fabric.Rect} instance from an SVG element 15068 * @static 15069 * @memberOf fabric.Rect 15070 * @param {SVGElement} element Element to parse 15071 * @param {Object} [options] Options object 15072 * @return {fabric.Rect} Instance of fabric.Rect 15073 */ 15074 fabric.Rect.fromElement = function(element, options) { 15075 if (!element) { 15076 return null; 15077 } 15078 options = options || { }; 15079 15080 var parsedAttributes = fabric.parseAttributes(element, fabric.Rect.ATTRIBUTE_NAMES); 15081 15082 parsedAttributes.left = parsedAttributes.left || 0; 15083 parsedAttributes.top = parsedAttributes.top || 0; 15084 var rect = new fabric.Rect(extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes)); 15085 rect.visible = rect.width > 0 && rect.height > 0; 15086 return rect; 15087 }; 15088 /* _FROM_SVG_END_ */ 15089 15090 /** 15091 * Returns {@link fabric.Rect} instance from an object representation 15092 * @static 15093 * @memberOf fabric.Rect 15094 * @param {Object} object Object to create an instance from 15095 * @return {Object} instance of fabric.Rect 15096 */ 15097 fabric.Rect.fromObject = function(object) { 15098 return new fabric.Rect(object); 15099 }; 15100 15101 })(typeof exports !== 'undefined' ? exports : this); 15102 15103 15104 (function(global) { 15105 15106 'use strict'; 15107 15108 var fabric = global.fabric || (global.fabric = { }); 15109 15110 if (fabric.Polyline) { 15111 fabric.warn('fabric.Polyline is already defined'); 15112 return; 15113 } 15114 15115 /** 15116 * Polyline class 15117 * @class fabric.Polyline 15118 * @extends fabric.Object 15119 * @see {@link fabric.Polyline#initialize} for constructor definition 15120 */ 15121 fabric.Polyline = fabric.util.createClass(fabric.Object, /** @lends fabric.Polyline.prototype */ { 15122 15123 /** 15124 * Type of an object 15125 * @type String 15126 * @default 15127 */ 15128 type: 'polyline', 15129 15130 /** 15131 * Points array 15132 * @type Array 15133 * @default 15134 */ 15135 points: null, 15136 15137 /** 15138 * Minimum X from points values, necessary to offset points 15139 * @type Number 15140 * @default 15141 */ 15142 minX: 0, 15143 15144 /** 15145 * Minimum Y from points values, necessary to offset points 15146 * @type Number 15147 * @default 15148 */ 15149 minY: 0, 15150 15151 /** 15152 * Constructor 15153 * @param {Array} points Array of points (where each point is an object with x and y) 15154 * @param {Object} [options] Options object 15155 * @param {Boolean} [skipOffset] Whether points offsetting should be skipped 15156 * @return {fabric.Polyline} thisArg 15157 * @example 15158 * var poly = new fabric.Polyline([ 15159 * { x: 10, y: 10 }, 15160 * { x: 50, y: 30 }, 15161 * { x: 40, y: 70 }, 15162 * { x: 60, y: 50 }, 15163 * { x: 100, y: 150 }, 15164 * { x: 40, y: 100 } 15165 * ], { 15166 * stroke: 'red', 15167 * left: 100, 15168 * top: 100 15169 * }); 15170 */ 15171 initialize: function(points, options) { 15172 return fabric.Polygon.prototype.initialize.call(this, points, options); 15173 }, 15174 15175 /** 15176 * @private 15177 */ 15178 _calcDimensions: function() { 15179 return fabric.Polygon.prototype._calcDimensions.call(this); 15180 }, 15181 15182 /** 15183 * @private 15184 */ 15185 _applyPointOffset: function() { 15186 return fabric.Polygon.prototype._applyPointOffset.call(this); 15187 }, 15188 15189 /** 15190 * Returns object representation of an instance 15191 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 15192 * @return {Object} Object representation of an instance 15193 */ 15194 toObject: function(propertiesToInclude) { 15195 return fabric.Polygon.prototype.toObject.call(this, propertiesToInclude); 15196 }, 15197 15198 /* _TO_SVG_START_ */ 15199 /** 15200 * Returns SVG representation of an instance 15201 * @param {Function} [reviver] Method for further parsing of svg representation. 15202 * @return {String} svg representation of an instance 15203 */ 15204 toSVG: function(reviver) { 15205 return fabric.Polygon.prototype.toSVG.call(this, reviver); 15206 }, 15207 /* _TO_SVG_END_ */ 15208 15209 /** 15210 * @private 15211 * @param {CanvasRenderingContext2D} ctx Context to render on 15212 */ 15213 _render: function(ctx) { 15214 if (!fabric.Polygon.prototype.commonRender.call(this, ctx)) { 15215 return; 15216 } 15217 this._renderFill(ctx); 15218 this._renderStroke(ctx); 15219 }, 15220 15221 /** 15222 * @private 15223 * @param {CanvasRenderingContext2D} ctx Context to render on 15224 */ 15225 _renderDashedStroke: function(ctx) { 15226 var p1, p2; 15227 15228 ctx.beginPath(); 15229 for (var i = 0, len = this.points.length; i < len; i++) { 15230 p1 = this.points[i]; 15231 p2 = this.points[i + 1] || p1; 15232 fabric.util.drawDashedLine(ctx, p1.x, p1.y, p2.x, p2.y, this.strokeDashArray); 15233 } 15234 }, 15235 15236 /** 15237 * Returns complexity of an instance 15238 * @return {Number} complexity of this instance 15239 */ 15240 complexity: function() { 15241 return this.get('points').length; 15242 } 15243 }); 15244 15245 /* _FROM_SVG_START_ */ 15246 /** 15247 * List of attribute names to account for when parsing SVG element (used by {@link fabric.Polyline.fromElement}) 15248 * @static 15249 * @memberOf fabric.Polyline 15250 * @see: http://www.w3.org/TR/SVG/shapes.html#PolylineElement 15251 */ 15252 fabric.Polyline.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); 15253 15254 /** 15255 * Returns fabric.Polyline instance from an SVG element 15256 * @static 15257 * @memberOf fabric.Polyline 15258 * @param {SVGElement} element Element to parse 15259 * @param {Object} [options] Options object 15260 * @return {fabric.Polyline} Instance of fabric.Polyline 15261 */ 15262 fabric.Polyline.fromElement = function(element, options) { 15263 if (!element) { 15264 return null; 15265 } 15266 options || (options = { }); 15267 15268 var points = fabric.parsePointsAttribute(element.getAttribute('points')), 15269 parsedAttributes = fabric.parseAttributes(element, fabric.Polyline.ATTRIBUTE_NAMES); 15270 15271 return new fabric.Polyline(points, fabric.util.object.extend(parsedAttributes, options)); 15272 }; 15273 /* _FROM_SVG_END_ */ 15274 15275 /** 15276 * Returns fabric.Polyline instance from an object representation 15277 * @static 15278 * @memberOf fabric.Polyline 15279 * @param {Object} object Object to create an instance from 15280 * @return {fabric.Polyline} Instance of fabric.Polyline 15281 */ 15282 fabric.Polyline.fromObject = function(object) { 15283 var points = object.points; 15284 return new fabric.Polyline(points, object, true); 15285 }; 15286 15287 })(typeof exports !== 'undefined' ? exports : this); 15288 15289 15290 (function(global) { 15291 15292 'use strict'; 15293 15294 var fabric = global.fabric || (global.fabric = { }), 15295 extend = fabric.util.object.extend, 15296 min = fabric.util.array.min, 15297 max = fabric.util.array.max, 15298 toFixed = fabric.util.toFixed; 15299 15300 if (fabric.Polygon) { 15301 fabric.warn('fabric.Polygon is already defined'); 15302 return; 15303 } 15304 15305 /** 15306 * Polygon class 15307 * @class fabric.Polygon 15308 * @extends fabric.Object 15309 * @see {@link fabric.Polygon#initialize} for constructor definition 15310 */ 15311 fabric.Polygon = fabric.util.createClass(fabric.Object, /** @lends fabric.Polygon.prototype */ { 15312 15313 /** 15314 * Type of an object 15315 * @type String 15316 * @default 15317 */ 15318 type: 'polygon', 15319 15320 /** 15321 * Points array 15322 * @type Array 15323 * @default 15324 */ 15325 points: null, 15326 15327 /** 15328 * Minimum X from points values, necessary to offset points 15329 * @type Number 15330 * @default 15331 */ 15332 minX: 0, 15333 15334 /** 15335 * Minimum Y from points values, necessary to offset points 15336 * @type Number 15337 * @default 15338 */ 15339 minY: 0, 15340 15341 /** 15342 * Constructor 15343 * @param {Array} points Array of points 15344 * @param {Object} [options] Options object 15345 * @return {fabric.Polygon} thisArg 15346 */ 15347 initialize: function(points, options) { 15348 options = options || { }; 15349 this.points = points || [ ]; 15350 this.callSuper('initialize', options); 15351 this._calcDimensions(); 15352 if (!('top' in options)) { 15353 this.top = this.minY; 15354 } 15355 if (!('left' in options)) { 15356 this.left = this.minX; 15357 } 15358 this.pathOffset = { 15359 x: this.minX + this.width / 2, 15360 y: this.minY + this.height / 2 15361 }; 15362 }, 15363 15364 /** 15365 * @private 15366 */ 15367 _calcDimensions: function() { 15368 15369 var points = this.points, 15370 minX = min(points, 'x'), 15371 minY = min(points, 'y'), 15372 maxX = max(points, 'x'), 15373 maxY = max(points, 'y'); 15374 15375 this.width = (maxX - minX) || 0; 15376 this.height = (maxY - minY) || 0; 15377 15378 this.minX = minX || 0, 15379 this.minY = minY || 0; 15380 }, 15381 15382 /** 15383 * Returns object representation of an instance 15384 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 15385 * @return {Object} Object representation of an instance 15386 */ 15387 toObject: function(propertiesToInclude) { 15388 return extend(this.callSuper('toObject', propertiesToInclude), { 15389 points: this.points.concat() 15390 }); 15391 }, 15392 15393 /* _TO_SVG_START_ */ 15394 /** 15395 * Returns svg representation of an instance 15396 * @param {Function} [reviver] Method for further parsing of svg representation. 15397 * @return {String} svg representation of an instance 15398 */ 15399 toSVG: function(reviver) { 15400 var points = [], addTransform, 15401 markup = this._createBaseSVGMarkup(); 15402 15403 for (var i = 0, len = this.points.length; i < len; i++) { 15404 points.push(toFixed(this.points[i].x, 2), ',', toFixed(this.points[i].y, 2), ' '); 15405 } 15406 if (!(this.group && this.group.type === 'path-group')) { 15407 addTransform = ' translate(' + (-this.pathOffset.x) + ', ' + (-this.pathOffset.y) + ') '; 15408 } 15409 markup.push( 15410 '<', this.type, ' ', 15411 'points="', points.join(''), 15412 '" style="', this.getSvgStyles(), 15413 '" transform="', this.getSvgTransform(), addTransform, 15414 ' ', this.getSvgTransformMatrix(), 15415 '"/>\n' 15416 ); 15417 15418 return reviver ? reviver(markup.join('')) : markup.join(''); 15419 }, 15420 /* _TO_SVG_END_ */ 15421 15422 /** 15423 * @private 15424 * @param {CanvasRenderingContext2D} ctx Context to render on 15425 */ 15426 _render: function(ctx, noTransform) { 15427 if (!this.commonRender(ctx, noTransform)) { 15428 return; 15429 } 15430 this._renderFill(ctx); 15431 if (this.stroke || this.strokeDashArray) { 15432 ctx.closePath(); 15433 this._renderStroke(ctx); 15434 } 15435 }, 15436 15437 /** 15438 * @private 15439 * @param {CanvasRenderingContext2D} ctx Context to render on 15440 */ 15441 commonRender: function(ctx, noTransform) { 15442 var point, len = this.points.length; 15443 15444 if (!len || isNaN(this.points[len - 1].y)) { 15445 // do not draw if no points or odd points 15446 // NaN comes from parseFloat of a empty string in parser 15447 return false; 15448 } 15449 15450 noTransform || ctx.translate(-this.pathOffset.x, -this.pathOffset.y); 15451 ctx.beginPath(); 15452 ctx.moveTo(this.points[0].x, this.points[0].y); 15453 for (var i = 0; i < len; i++) { 15454 point = this.points[i]; 15455 ctx.lineTo(point.x, point.y); 15456 } 15457 return true; 15458 }, 15459 15460 /** 15461 * @private 15462 * @param {CanvasRenderingContext2D} ctx Context to render on 15463 */ 15464 _renderDashedStroke: function(ctx) { 15465 fabric.Polyline.prototype._renderDashedStroke.call(this, ctx); 15466 ctx.closePath(); 15467 }, 15468 15469 /** 15470 * Returns complexity of an instance 15471 * @return {Number} complexity of this instance 15472 */ 15473 complexity: function() { 15474 return this.points.length; 15475 } 15476 }); 15477 15478 /* _FROM_SVG_START_ */ 15479 /** 15480 * List of attribute names to account for when parsing SVG element (used by `fabric.Polygon.fromElement`) 15481 * @static 15482 * @memberOf fabric.Polygon 15483 * @see: http://www.w3.org/TR/SVG/shapes.html#PolygonElement 15484 */ 15485 fabric.Polygon.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); 15486 15487 /** 15488 * Returns {@link fabric.Polygon} instance from an SVG element 15489 * @static 15490 * @memberOf fabric.Polygon 15491 * @param {SVGElement} element Element to parse 15492 * @param {Object} [options] Options object 15493 * @return {fabric.Polygon} Instance of fabric.Polygon 15494 */ 15495 fabric.Polygon.fromElement = function(element, options) { 15496 if (!element) { 15497 return null; 15498 } 15499 15500 options || (options = { }); 15501 15502 var points = fabric.parsePointsAttribute(element.getAttribute('points')), 15503 parsedAttributes = fabric.parseAttributes(element, fabric.Polygon.ATTRIBUTE_NAMES); 15504 15505 return new fabric.Polygon(points, extend(parsedAttributes, options)); 15506 }; 15507 /* _FROM_SVG_END_ */ 15508 15509 /** 15510 * Returns fabric.Polygon instance from an object representation 15511 * @static 15512 * @memberOf fabric.Polygon 15513 * @param {Object} object Object to create an instance from 15514 * @return {fabric.Polygon} Instance of fabric.Polygon 15515 */ 15516 fabric.Polygon.fromObject = function(object) { 15517 return new fabric.Polygon(object.points, object, true); 15518 }; 15519 15520 })(typeof exports !== 'undefined' ? exports : this); 15521 15522 15523 (function(global) { 15524 15525 'use strict'; 15526 15527 var fabric = global.fabric || (global.fabric = { }), 15528 min = fabric.util.array.min, 15529 max = fabric.util.array.max, 15530 extend = fabric.util.object.extend, 15531 _toString = Object.prototype.toString, 15532 drawArc = fabric.util.drawArc, 15533 commandLengths = { 15534 m: 2, 15535 l: 2, 15536 h: 1, 15537 v: 1, 15538 c: 6, 15539 s: 4, 15540 q: 4, 15541 t: 2, 15542 a: 7 15543 }, 15544 repeatedCommands = { 15545 m: 'l', 15546 M: 'L' 15547 }; 15548 15549 if (fabric.Path) { 15550 fabric.warn('fabric.Path is already defined'); 15551 return; 15552 } 15553 15554 /** 15555 * Path class 15556 * @class fabric.Path 15557 * @extends fabric.Object 15558 * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#path_and_pathgroup} 15559 * @see {@link fabric.Path#initialize} for constructor definition 15560 */ 15561 fabric.Path = fabric.util.createClass(fabric.Object, /** @lends fabric.Path.prototype */ { 15562 15563 /** 15564 * Type of an object 15565 * @type String 15566 * @default 15567 */ 15568 type: 'path', 15569 15570 /** 15571 * Array of path points 15572 * @type Array 15573 * @default 15574 */ 15575 path: null, 15576 15577 /** 15578 * Minimum X from points values, necessary to offset points 15579 * @type Number 15580 * @default 15581 */ 15582 minX: 0, 15583 15584 /** 15585 * Minimum Y from points values, necessary to offset points 15586 * @type Number 15587 * @default 15588 */ 15589 minY: 0, 15590 15591 /** 15592 * Constructor 15593 * @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens) 15594 * @param {Object} [options] Options object 15595 * @return {fabric.Path} thisArg 15596 */ 15597 initialize: function(path, options) { 15598 options = options || { }; 15599 15600 this.setOptions(options); 15601 15602 if (!path) { 15603 path = [ ]; 15604 } 15605 15606 var fromArray = _toString.call(path) === '[object Array]'; 15607 15608 this.path = fromArray 15609 ? path 15610 // one of commands (m,M,l,L,q,Q,c,C,etc.) followed by non-command characters (i.e. command values) 15611 : path.match && path.match(/[mzlhvcsqta][^mzlhvcsqta]*/gi); 15612 15613 if (!this.path) { 15614 return; 15615 } 15616 15617 if (!fromArray) { 15618 this.path = this._parsePath(); 15619 } 15620 15621 this._setPositionDimensions(options); 15622 15623 if (options.sourcePath) { 15624 this.setSourcePath(options.sourcePath); 15625 } 15626 }, 15627 15628 /** 15629 * @private 15630 * @param {Object} options Options object 15631 */ 15632 _setPositionDimensions: function(options) { 15633 var calcDim = this._parseDimensions(); 15634 15635 this.minX = calcDim.left; 15636 this.minY = calcDim.top; 15637 this.width = calcDim.width; 15638 this.height = calcDim.height; 15639 15640 if (typeof options.left === 'undefined') { 15641 this.left = calcDim.left + (this.originX === 'center' 15642 ? this.width / 2 15643 : this.originX === 'right' 15644 ? this.width 15645 : 0); 15646 } 15647 15648 if (typeof options.top === 'undefined') { 15649 this.top = calcDim.top + (this.originY === 'center' 15650 ? this.height / 2 15651 : this.originY === 'bottom' 15652 ? this.height 15653 : 0); 15654 } 15655 15656 this.pathOffset = this.pathOffset || { 15657 x: this.minX + this.width / 2, 15658 y: this.minY + this.height / 2 15659 }; 15660 }, 15661 15662 /** 15663 * @private 15664 * @param {CanvasRenderingContext2D} ctx context to render path on 15665 */ 15666 _render: function(ctx) { 15667 var current, // current instruction 15668 previous = null, 15669 subpathStartX = 0, 15670 subpathStartY = 0, 15671 x = 0, // current x 15672 y = 0, // current y 15673 controlX = 0, // current control point x 15674 controlY = 0, // current control point y 15675 tempX, 15676 tempY, 15677 l = -this.pathOffset.x, 15678 t = -this.pathOffset.y; 15679 15680 if (this.group && this.group.type === 'path-group') { 15681 l = 0; 15682 t = 0; 15683 } 15684 15685 ctx.beginPath(); 15686 15687 for (var i = 0, len = this.path.length; i < len; ++i) { 15688 15689 current = this.path[i]; 15690 15691 switch (current[0]) { // first letter 15692 15693 case 'l': // lineto, relative 15694 x += current[1]; 15695 y += current[2]; 15696 ctx.lineTo(x + l, y + t); 15697 break; 15698 15699 case 'L': // lineto, absolute 15700 x = current[1]; 15701 y = current[2]; 15702 ctx.lineTo(x + l, y + t); 15703 break; 15704 15705 case 'h': // horizontal lineto, relative 15706 x += current[1]; 15707 ctx.lineTo(x + l, y + t); 15708 break; 15709 15710 case 'H': // horizontal lineto, absolute 15711 x = current[1]; 15712 ctx.lineTo(x + l, y + t); 15713 break; 15714 15715 case 'v': // vertical lineto, relative 15716 y += current[1]; 15717 ctx.lineTo(x + l, y + t); 15718 break; 15719 15720 case 'V': // verical lineto, absolute 15721 y = current[1]; 15722 ctx.lineTo(x + l, y + t); 15723 break; 15724 15725 case 'm': // moveTo, relative 15726 x += current[1]; 15727 y += current[2]; 15728 subpathStartX = x; 15729 subpathStartY = y; 15730 ctx.moveTo(x + l, y + t); 15731 break; 15732 15733 case 'M': // moveTo, absolute 15734 x = current[1]; 15735 y = current[2]; 15736 subpathStartX = x; 15737 subpathStartY = y; 15738 ctx.moveTo(x + l, y + t); 15739 break; 15740 15741 case 'c': // bezierCurveTo, relative 15742 tempX = x + current[5]; 15743 tempY = y + current[6]; 15744 controlX = x + current[3]; 15745 controlY = y + current[4]; 15746 ctx.bezierCurveTo( 15747 x + current[1] + l, // x1 15748 y + current[2] + t, // y1 15749 controlX + l, // x2 15750 controlY + t, // y2 15751 tempX + l, 15752 tempY + t 15753 ); 15754 x = tempX; 15755 y = tempY; 15756 break; 15757 15758 case 'C': // bezierCurveTo, absolute 15759 x = current[5]; 15760 y = current[6]; 15761 controlX = current[3]; 15762 controlY = current[4]; 15763 ctx.bezierCurveTo( 15764 current[1] + l, 15765 current[2] + t, 15766 controlX + l, 15767 controlY + t, 15768 x + l, 15769 y + t 15770 ); 15771 break; 15772 15773 case 's': // shorthand cubic bezierCurveTo, relative 15774 15775 // transform to absolute x,y 15776 tempX = x + current[3]; 15777 tempY = y + current[4]; 15778 15779 if (previous[0].match(/[CcSs]/) === null) { 15780 // If there is no previous command or if the previous command was not a C, c, S, or s, 15781 // the control point is coincident with the current point 15782 controlX = x; 15783 controlY = y; 15784 } 15785 else { 15786 // calculate reflection of previous control points 15787 controlX = 2 * x - controlX; 15788 controlY = 2 * y - controlY; 15789 } 15790 15791 ctx.bezierCurveTo( 15792 controlX + l, 15793 controlY + t, 15794 x + current[1] + l, 15795 y + current[2] + t, 15796 tempX + l, 15797 tempY + t 15798 ); 15799 // set control point to 2nd one of this command 15800 // "... the first control point is assumed to be 15801 // the reflection of the second control point on 15802 // the previous command relative to the current point." 15803 controlX = x + current[1]; 15804 controlY = y + current[2]; 15805 15806 x = tempX; 15807 y = tempY; 15808 break; 15809 15810 case 'S': // shorthand cubic bezierCurveTo, absolute 15811 tempX = current[3]; 15812 tempY = current[4]; 15813 if (previous[0].match(/[CcSs]/) === null) { 15814 // If there is no previous command or if the previous command was not a C, c, S, or s, 15815 // the control point is coincident with the current point 15816 controlX = x; 15817 controlY = y; 15818 } 15819 else { 15820 // calculate reflection of previous control points 15821 controlX = 2 * x - controlX; 15822 controlY = 2 * y - controlY; 15823 } 15824 ctx.bezierCurveTo( 15825 controlX + l, 15826 controlY + t, 15827 current[1] + l, 15828 current[2] + t, 15829 tempX + l, 15830 tempY + t 15831 ); 15832 x = tempX; 15833 y = tempY; 15834 15835 // set control point to 2nd one of this command 15836 // "... the first control point is assumed to be 15837 // the reflection of the second control point on 15838 // the previous command relative to the current point." 15839 controlX = current[1]; 15840 controlY = current[2]; 15841 15842 break; 15843 15844 case 'q': // quadraticCurveTo, relative 15845 // transform to absolute x,y 15846 tempX = x + current[3]; 15847 tempY = y + current[4]; 15848 15849 controlX = x + current[1]; 15850 controlY = y + current[2]; 15851 15852 ctx.quadraticCurveTo( 15853 controlX + l, 15854 controlY + t, 15855 tempX + l, 15856 tempY + t 15857 ); 15858 x = tempX; 15859 y = tempY; 15860 break; 15861 15862 case 'Q': // quadraticCurveTo, absolute 15863 tempX = current[3]; 15864 tempY = current[4]; 15865 15866 ctx.quadraticCurveTo( 15867 current[1] + l, 15868 current[2] + t, 15869 tempX + l, 15870 tempY + t 15871 ); 15872 x = tempX; 15873 y = tempY; 15874 controlX = current[1]; 15875 controlY = current[2]; 15876 break; 15877 15878 case 't': // shorthand quadraticCurveTo, relative 15879 15880 // transform to absolute x,y 15881 tempX = x + current[1]; 15882 tempY = y + current[2]; 15883 15884 if (previous[0].match(/[QqTt]/) === null) { 15885 // If there is no previous command or if the previous command was not a Q, q, T or t, 15886 // assume the control point is coincident with the current point 15887 controlX = x; 15888 controlY = y; 15889 } 15890 else { 15891 // calculate reflection of previous control point 15892 controlX = 2 * x - controlX; 15893 controlY = 2 * y - controlY; 15894 } 15895 15896 ctx.quadraticCurveTo( 15897 controlX + l, 15898 controlY + t, 15899 tempX + l, 15900 tempY + t 15901 ); 15902 x = tempX; 15903 y = tempY; 15904 15905 break; 15906 15907 case 'T': 15908 tempX = current[1]; 15909 tempY = current[2]; 15910 15911 if (previous[0].match(/[QqTt]/) === null) { 15912 // If there is no previous command or if the previous command was not a Q, q, T or t, 15913 // assume the control point is coincident with the current point 15914 controlX = x; 15915 controlY = y; 15916 } 15917 else { 15918 // calculate reflection of previous control point 15919 controlX = 2 * x - controlX; 15920 controlY = 2 * y - controlY; 15921 } 15922 ctx.quadraticCurveTo( 15923 controlX + l, 15924 controlY + t, 15925 tempX + l, 15926 tempY + t 15927 ); 15928 x = tempX; 15929 y = tempY; 15930 break; 15931 15932 case 'a': 15933 // TODO: optimize this 15934 drawArc(ctx, x + l, y + t, [ 15935 current[1], 15936 current[2], 15937 current[3], 15938 current[4], 15939 current[5], 15940 current[6] + x + l, 15941 current[7] + y + t 15942 ]); 15943 x += current[6]; 15944 y += current[7]; 15945 break; 15946 15947 case 'A': 15948 // TODO: optimize this 15949 drawArc(ctx, x + l, y + t, [ 15950 current[1], 15951 current[2], 15952 current[3], 15953 current[4], 15954 current[5], 15955 current[6] + l, 15956 current[7] + t 15957 ]); 15958 x = current[6]; 15959 y = current[7]; 15960 break; 15961 15962 case 'z': 15963 case 'Z': 15964 x = subpathStartX; 15965 y = subpathStartY; 15966 ctx.closePath(); 15967 break; 15968 } 15969 previous = current; 15970 } 15971 this._renderFill(ctx); 15972 this._renderStroke(ctx); 15973 }, 15974 15975 /** 15976 * Returns string representation of an instance 15977 * @return {String} string representation of an instance 15978 */ 15979 toString: function() { 15980 return '#<fabric.Path (' + this.complexity() + 15981 '): { "top": ' + this.top + ', "left": ' + this.left + ' }>'; 15982 }, 15983 15984 /** 15985 * Returns object representation of an instance 15986 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 15987 * @return {Object} object representation of an instance 15988 */ 15989 toObject: function(propertiesToInclude) { 15990 var o = extend(this.callSuper('toObject', propertiesToInclude), { 15991 path: this.path.map(function(item) { return item.slice() }), 15992 pathOffset: this.pathOffset 15993 }); 15994 if (this.sourcePath) { 15995 o.sourcePath = this.sourcePath; 15996 } 15997 if (this.transformMatrix) { 15998 o.transformMatrix = this.transformMatrix; 15999 } 16000 return o; 16001 }, 16002 16003 /** 16004 * Returns dataless object representation of an instance 16005 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 16006 * @return {Object} object representation of an instance 16007 */ 16008 toDatalessObject: function(propertiesToInclude) { 16009 var o = this.toObject(propertiesToInclude); 16010 if (this.sourcePath) { 16011 o.path = this.sourcePath; 16012 } 16013 delete o.sourcePath; 16014 return o; 16015 }, 16016 16017 /* _TO_SVG_START_ */ 16018 /** 16019 * Returns svg representation of an instance 16020 * @param {Function} [reviver] Method for further parsing of svg representation. 16021 * @return {String} svg representation of an instance 16022 */ 16023 toSVG: function(reviver) { 16024 var chunks = [], 16025 markup = this._createBaseSVGMarkup(), addTransform = ''; 16026 16027 for (var i = 0, len = this.path.length; i < len; i++) { 16028 chunks.push(this.path[i].join(' ')); 16029 } 16030 var path = chunks.join(' '); 16031 if (!(this.group && this.group.type === 'path-group')) { 16032 addTransform = ' translate(' + (-this.pathOffset.x) + ', ' + (-this.pathOffset.y) + ') '; 16033 } 16034 markup.push( 16035 //jscs:disable validateIndentation 16036 '<path ', 16037 'd="', path, 16038 '" style="', this.getSvgStyles(), 16039 '" transform="', this.getSvgTransform(), addTransform, 16040 this.getSvgTransformMatrix(), '" stroke-linecap="round" ', 16041 '/>\n' 16042 //jscs:enable validateIndentation 16043 ); 16044 16045 return reviver ? reviver(markup.join('')) : markup.join(''); 16046 }, 16047 /* _TO_SVG_END_ */ 16048 16049 /** 16050 * Returns number representation of an instance complexity 16051 * @return {Number} complexity of this instance 16052 */ 16053 complexity: function() { 16054 return this.path.length; 16055 }, 16056 16057 /** 16058 * @private 16059 */ 16060 _parsePath: function() { 16061 var result = [ ], 16062 coords = [ ], 16063 currentPath, 16064 parsed, 16065 re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/ig, 16066 match, 16067 coordsStr; 16068 16069 for (var i = 0, coordsParsed, len = this.path.length; i < len; i++) { 16070 currentPath = this.path[i]; 16071 16072 coordsStr = currentPath.slice(1).trim(); 16073 coords.length = 0; 16074 16075 while ((match = re.exec(coordsStr))) { 16076 coords.push(match[0]); 16077 } 16078 16079 coordsParsed = [ currentPath.charAt(0) ]; 16080 16081 for (var j = 0, jlen = coords.length; j < jlen; j++) { 16082 parsed = parseFloat(coords[j]); 16083 if (!isNaN(parsed)) { 16084 coordsParsed.push(parsed); 16085 } 16086 } 16087 16088 var command = coordsParsed[0], 16089 commandLength = commandLengths[command.toLowerCase()], 16090 repeatedCommand = repeatedCommands[command] || command; 16091 16092 if (coordsParsed.length - 1 > commandLength) { 16093 for (var k = 1, klen = coordsParsed.length; k < klen; k += commandLength) { 16094 result.push([ command ].concat(coordsParsed.slice(k, k + commandLength))); 16095 command = repeatedCommand; 16096 } 16097 } 16098 else { 16099 result.push(coordsParsed); 16100 } 16101 } 16102 16103 return result; 16104 }, 16105 16106 /** 16107 * @private 16108 */ 16109 _parseDimensions: function() { 16110 16111 var aX = [], 16112 aY = [], 16113 current, // current instruction 16114 previous = null, 16115 subpathStartX = 0, 16116 subpathStartY = 0, 16117 x = 0, // current x 16118 y = 0, // current y 16119 controlX = 0, // current control point x 16120 controlY = 0, // current control point y 16121 tempX, 16122 tempY, 16123 bounds; 16124 16125 for (var i = 0, len = this.path.length; i < len; ++i) { 16126 16127 current = this.path[i]; 16128 16129 switch (current[0]) { // first letter 16130 16131 case 'l': // lineto, relative 16132 x += current[1]; 16133 y += current[2]; 16134 bounds = [ ]; 16135 break; 16136 16137 case 'L': // lineto, absolute 16138 x = current[1]; 16139 y = current[2]; 16140 bounds = [ ]; 16141 break; 16142 16143 case 'h': // horizontal lineto, relative 16144 x += current[1]; 16145 bounds = [ ]; 16146 break; 16147 16148 case 'H': // horizontal lineto, absolute 16149 x = current[1]; 16150 bounds = [ ]; 16151 break; 16152 16153 case 'v': // vertical lineto, relative 16154 y += current[1]; 16155 bounds = [ ]; 16156 break; 16157 16158 case 'V': // verical lineto, absolute 16159 y = current[1]; 16160 bounds = [ ]; 16161 break; 16162 16163 case 'm': // moveTo, relative 16164 x += current[1]; 16165 y += current[2]; 16166 subpathStartX = x; 16167 subpathStartY = y; 16168 bounds = [ ]; 16169 break; 16170 16171 case 'M': // moveTo, absolute 16172 x = current[1]; 16173 y = current[2]; 16174 subpathStartX = x; 16175 subpathStartY = y; 16176 bounds = [ ]; 16177 break; 16178 16179 case 'c': // bezierCurveTo, relative 16180 tempX = x + current[5]; 16181 tempY = y + current[6]; 16182 controlX = x + current[3]; 16183 controlY = y + current[4]; 16184 bounds = fabric.util.getBoundsOfCurve(x, y, 16185 x + current[1], // x1 16186 y + current[2], // y1 16187 controlX, // x2 16188 controlY, // y2 16189 tempX, 16190 tempY 16191 ); 16192 x = tempX; 16193 y = tempY; 16194 break; 16195 16196 case 'C': // bezierCurveTo, absolute 16197 x = current[5]; 16198 y = current[6]; 16199 controlX = current[3]; 16200 controlY = current[4]; 16201 bounds = fabric.util.getBoundsOfCurve(x, y, 16202 current[1], 16203 current[2], 16204 controlX, 16205 controlY, 16206 x, 16207 y 16208 ); 16209 break; 16210 16211 case 's': // shorthand cubic bezierCurveTo, relative 16212 16213 // transform to absolute x,y 16214 tempX = x + current[3]; 16215 tempY = y + current[4]; 16216 16217 if (previous[0].match(/[CcSs]/) === null) { 16218 // If there is no previous command or if the previous command was not a C, c, S, or s, 16219 // the control point is coincident with the current point 16220 controlX = x; 16221 controlY = y; 16222 } 16223 else { 16224 // calculate reflection of previous control points 16225 controlX = 2 * x - controlX; 16226 controlY = 2 * y - controlY; 16227 } 16228 16229 bounds = fabric.util.getBoundsOfCurve(x, y, 16230 controlX, 16231 controlY, 16232 x + current[1], 16233 y + current[2], 16234 tempX, 16235 tempY 16236 ); 16237 // set control point to 2nd one of this command 16238 // "... the first control point is assumed to be 16239 // the reflection of the second control point on 16240 // the previous command relative to the current point." 16241 controlX = x + current[1]; 16242 controlY = y + current[2]; 16243 x = tempX; 16244 y = tempY; 16245 break; 16246 16247 case 'S': // shorthand cubic bezierCurveTo, absolute 16248 tempX = current[3]; 16249 tempY = current[4]; 16250 if (previous[0].match(/[CcSs]/) === null) { 16251 // If there is no previous command or if the previous command was not a C, c, S, or s, 16252 // the control point is coincident with the current point 16253 controlX = x; 16254 controlY = y; 16255 } 16256 else { 16257 // calculate reflection of previous control points 16258 controlX = 2 * x - controlX; 16259 controlY = 2 * y - controlY; 16260 } 16261 bounds = fabric.util.getBoundsOfCurve(x, y, 16262 controlX, 16263 controlY, 16264 current[1], 16265 current[2], 16266 tempX, 16267 tempY 16268 ); 16269 x = tempX; 16270 y = tempY; 16271 // set control point to 2nd one of this command 16272 // "... the first control point is assumed to be 16273 // the reflection of the second control point on 16274 // the previous command relative to the current point." 16275 controlX = current[1]; 16276 controlY = current[2]; 16277 break; 16278 16279 case 'q': // quadraticCurveTo, relative 16280 // transform to absolute x,y 16281 tempX = x + current[3]; 16282 tempY = y + current[4]; 16283 controlX = x + current[1]; 16284 controlY = y + current[2]; 16285 bounds = fabric.util.getBoundsOfCurve(x, y, 16286 controlX, 16287 controlY, 16288 controlX, 16289 controlY, 16290 tempX, 16291 tempY 16292 ); 16293 x = tempX; 16294 y = tempY; 16295 break; 16296 16297 case 'Q': // quadraticCurveTo, absolute 16298 controlX = current[1]; 16299 controlY = current[2]; 16300 bounds = fabric.util.getBoundsOfCurve(x, y, 16301 controlX, 16302 controlY, 16303 controlX, 16304 controlY, 16305 current[3], 16306 current[4] 16307 ); 16308 x = current[3]; 16309 y = current[4]; 16310 break; 16311 16312 case 't': // shorthand quadraticCurveTo, relative 16313 // transform to absolute x,y 16314 tempX = x + current[1]; 16315 tempY = y + current[2]; 16316 if (previous[0].match(/[QqTt]/) === null) { 16317 // If there is no previous command or if the previous command was not a Q, q, T or t, 16318 // assume the control point is coincident with the current point 16319 controlX = x; 16320 controlY = y; 16321 } 16322 else { 16323 // calculate reflection of previous control point 16324 controlX = 2 * x - controlX; 16325 controlY = 2 * y - controlY; 16326 } 16327 16328 bounds = fabric.util.getBoundsOfCurve(x, y, 16329 controlX, 16330 controlY, 16331 controlX, 16332 controlY, 16333 tempX, 16334 tempY 16335 ); 16336 x = tempX; 16337 y = tempY; 16338 16339 break; 16340 16341 case 'T': 16342 tempX = current[1]; 16343 tempY = current[2]; 16344 16345 if (previous[0].match(/[QqTt]/) === null) { 16346 // If there is no previous command or if the previous command was not a Q, q, T or t, 16347 // assume the control point is coincident with the current point 16348 controlX = x; 16349 controlY = y; 16350 } 16351 else { 16352 // calculate reflection of previous control point 16353 controlX = 2 * x - controlX; 16354 controlY = 2 * y - controlY; 16355 } 16356 bounds = fabric.util.getBoundsOfCurve(x, y, 16357 controlX, 16358 controlY, 16359 controlX, 16360 controlY, 16361 tempX, 16362 tempY 16363 ); 16364 x = tempX; 16365 y = tempY; 16366 break; 16367 16368 case 'a': 16369 // TODO: optimize this 16370 bounds = fabric.util.getBoundsOfArc(x, y, 16371 current[1], 16372 current[2], 16373 current[3], 16374 current[4], 16375 current[5], 16376 current[6] + x, 16377 current[7] + y 16378 ); 16379 x += current[6]; 16380 y += current[7]; 16381 break; 16382 16383 case 'A': 16384 // TODO: optimize this 16385 bounds = fabric.util.getBoundsOfArc(x, y, 16386 current[1], 16387 current[2], 16388 current[3], 16389 current[4], 16390 current[5], 16391 current[6], 16392 current[7] 16393 ); 16394 x = current[6]; 16395 y = current[7]; 16396 break; 16397 16398 case 'z': 16399 case 'Z': 16400 x = subpathStartX; 16401 y = subpathStartY; 16402 break; 16403 } 16404 previous = current; 16405 bounds.forEach(function (point) { 16406 aX.push(point.x); 16407 aY.push(point.y); 16408 }); 16409 aX.push(x); 16410 aY.push(y); 16411 } 16412 16413 var minX = min(aX) || 0, 16414 minY = min(aY) || 0, 16415 maxX = max(aX) || 0, 16416 maxY = max(aY) || 0, 16417 deltaX = maxX - minX, 16418 deltaY = maxY - minY, 16419 16420 o = { 16421 left: minX, 16422 top: minY, 16423 width: deltaX, 16424 height: deltaY 16425 }; 16426 16427 return o; 16428 } 16429 }); 16430 16431 /** 16432 * Creates an instance of fabric.Path from an object 16433 * @static 16434 * @memberOf fabric.Path 16435 * @param {Object} object 16436 * @param {Function} callback Callback to invoke when an fabric.Path instance is created 16437 */ 16438 fabric.Path.fromObject = function(object, callback) { 16439 if (typeof object.path === 'string') { 16440 fabric.loadSVGFromURL(object.path, function (elements) { 16441 var path = elements[0], 16442 pathUrl = object.path; 16443 16444 delete object.path; 16445 16446 fabric.util.object.extend(path, object); 16447 path.setSourcePath(pathUrl); 16448 16449 callback(path); 16450 }); 16451 } 16452 else { 16453 callback(new fabric.Path(object.path, object)); 16454 } 16455 }; 16456 16457 /* _FROM_SVG_START_ */ 16458 /** 16459 * List of attribute names to account for when parsing SVG element (used by `fabric.Path.fromElement`) 16460 * @static 16461 * @memberOf fabric.Path 16462 * @see http://www.w3.org/TR/SVG/paths.html#PathElement 16463 */ 16464 fabric.Path.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(['d']); 16465 16466 /** 16467 * Creates an instance of fabric.Path from an SVG <path> element 16468 * @static 16469 * @memberOf fabric.Path 16470 * @param {SVGElement} element to parse 16471 * @param {Function} callback Callback to invoke when an fabric.Path instance is created 16472 * @param {Object} [options] Options object 16473 */ 16474 fabric.Path.fromElement = function(element, callback, options) { 16475 var parsedAttributes = fabric.parseAttributes(element, fabric.Path.ATTRIBUTE_NAMES); 16476 callback && callback(new fabric.Path(parsedAttributes.d, extend(parsedAttributes, options))); 16477 }; 16478 /* _FROM_SVG_END_ */ 16479 16480 /** 16481 * Indicates that instances of this type are async 16482 * @static 16483 * @memberOf fabric.Path 16484 * @type Boolean 16485 * @default 16486 */ 16487 fabric.Path.async = true; 16488 16489 })(typeof exports !== 'undefined' ? exports : this); 16490 16491 16492 (function(global) { 16493 16494 'use strict'; 16495 16496 var fabric = global.fabric || (global.fabric = { }), 16497 extend = fabric.util.object.extend, 16498 invoke = fabric.util.array.invoke, 16499 parentToObject = fabric.Object.prototype.toObject; 16500 16501 if (fabric.PathGroup) { 16502 fabric.warn('fabric.PathGroup is already defined'); 16503 return; 16504 } 16505 16506 /** 16507 * Path group class 16508 * @class fabric.PathGroup 16509 * @extends fabric.Path 16510 * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#path_and_pathgroup} 16511 * @see {@link fabric.PathGroup#initialize} for constructor definition 16512 */ 16513 fabric.PathGroup = fabric.util.createClass(fabric.Path, /** @lends fabric.PathGroup.prototype */ { 16514 16515 /** 16516 * Type of an object 16517 * @type String 16518 * @default 16519 */ 16520 type: 'path-group', 16521 16522 /** 16523 * Fill value 16524 * @type String 16525 * @default 16526 */ 16527 fill: '', 16528 16529 /** 16530 * Constructor 16531 * @param {Array} paths 16532 * @param {Object} [options] Options object 16533 * @return {fabric.PathGroup} thisArg 16534 */ 16535 initialize: function(paths, options) { 16536 16537 options = options || { }; 16538 this.paths = paths || [ ]; 16539 16540 for (var i = this.paths.length; i--;) { 16541 this.paths[i].group = this; 16542 } 16543 16544 if (options.toBeParsed) { 16545 this.parseDimensionsFromPaths(options); 16546 delete options.toBeParsed; 16547 } 16548 this.setOptions(options); 16549 this.setCoords(); 16550 16551 if (options.sourcePath) { 16552 this.setSourcePath(options.sourcePath); 16553 } 16554 }, 16555 16556 /** 16557 * Calculate width and height based on paths contained 16558 */ 16559 parseDimensionsFromPaths: function(options) { 16560 var points, p, xC = [ ], yC = [ ], path, height, width, 16561 m; 16562 for (var j = this.paths.length; j--;) { 16563 path = this.paths[j]; 16564 height = path.height + path.strokeWidth; 16565 width = path.width + path.strokeWidth; 16566 points = [ 16567 { x: path.left, y: path.top }, 16568 { x: path.left + width, y: path.top }, 16569 { x: path.left, y: path.top + height }, 16570 { x: path.left + width, y: path.top + height } 16571 ]; 16572 m = this.paths[j].transformMatrix; 16573 for (var i = 0; i < points.length; i++) { 16574 p = points[i]; 16575 if (m) { 16576 p = fabric.util.transformPoint(p, m, false); 16577 } 16578 xC.push(p.x); 16579 yC.push(p.y); 16580 } 16581 } 16582 options.width = Math.max.apply(null, xC); 16583 options.height = Math.max.apply(null, yC); 16584 }, 16585 16586 /** 16587 * Renders this group on a specified context 16588 * @param {CanvasRenderingContext2D} ctx Context to render this instance on 16589 */ 16590 render: function(ctx) { 16591 // do not render if object is not visible 16592 if (!this.visible) { 16593 return; 16594 } 16595 16596 ctx.save(); 16597 16598 if (this.transformMatrix) { 16599 ctx.transform.apply(ctx, this.transformMatrix); 16600 } 16601 this.transform(ctx); 16602 16603 this._setShadow(ctx); 16604 this.clipTo && fabric.util.clipContext(this, ctx); 16605 ctx.translate(-this.width/2, -this.height/2); 16606 for (var i = 0, l = this.paths.length; i < l; ++i) { 16607 this.paths[i].render(ctx, true); 16608 } 16609 this.clipTo && ctx.restore(); 16610 ctx.restore(); 16611 }, 16612 16613 /** 16614 * Sets certain property to a certain value 16615 * @param {String} prop 16616 * @param {Any} value 16617 * @return {fabric.PathGroup} thisArg 16618 */ 16619 _set: function(prop, value) { 16620 16621 if (prop === 'fill' && value && this.isSameColor()) { 16622 var i = this.paths.length; 16623 while (i--) { 16624 this.paths[i]._set(prop, value); 16625 } 16626 } 16627 16628 return this.callSuper('_set', prop, value); 16629 }, 16630 16631 /** 16632 * Returns object representation of this path group 16633 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 16634 * @return {Object} object representation of an instance 16635 */ 16636 toObject: function(propertiesToInclude) { 16637 var o = extend(parentToObject.call(this, propertiesToInclude), { 16638 paths: invoke(this.getObjects(), 'toObject', propertiesToInclude) 16639 }); 16640 if (this.sourcePath) { 16641 o.sourcePath = this.sourcePath; 16642 } 16643 return o; 16644 }, 16645 16646 /** 16647 * Returns dataless object representation of this path group 16648 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 16649 * @return {Object} dataless object representation of an instance 16650 */ 16651 toDatalessObject: function(propertiesToInclude) { 16652 var o = this.toObject(propertiesToInclude); 16653 if (this.sourcePath) { 16654 o.paths = this.sourcePath; 16655 } 16656 return o; 16657 }, 16658 16659 /* _TO_SVG_START_ */ 16660 /** 16661 * Returns svg representation of an instance 16662 * @param {Function} [reviver] Method for further parsing of svg representation. 16663 * @return {String} svg representation of an instance 16664 */ 16665 toSVG: function(reviver) { 16666 var objects = this.getObjects(), 16667 p = this.getPointByOrigin('left', 'top'), 16668 translatePart = 'translate(' + p.x + ' ' + p.y + ')', 16669 markup = this._createBaseSVGMarkup(); 16670 markup.push( 16671 '<g ', 16672 'style="', this.getSvgStyles(), '" ', 16673 'transform="', this.getSvgTransformMatrix(), translatePart, this.getSvgTransform(), '" ', 16674 '>\n' 16675 ); 16676 16677 for (var i = 0, len = objects.length; i < len; i++) { 16678 markup.push('\t', objects[i].toSVG(reviver)); 16679 } 16680 markup.push('</g>\n'); 16681 16682 return reviver ? reviver(markup.join('')) : markup.join(''); 16683 }, 16684 /* _TO_SVG_END_ */ 16685 16686 /** 16687 * Returns a string representation of this path group 16688 * @return {String} string representation of an object 16689 */ 16690 toString: function() { 16691 return '#<fabric.PathGroup (' + this.complexity() + 16692 '): { top: ' + this.top + ', left: ' + this.left + ' }>'; 16693 }, 16694 16695 /** 16696 * Returns true if all paths in this group are of same color 16697 * @return {Boolean} true if all paths are of the same color (`fill`) 16698 */ 16699 isSameColor: function() { 16700 var firstPathFill = this.getObjects()[0].get('fill') || ''; 16701 if (typeof firstPathFill !== 'string') { 16702 return false; 16703 } 16704 firstPathFill = firstPathFill.toLowerCase(); 16705 return this.getObjects().every(function(path) { 16706 var pathFill = path.get('fill') || ''; 16707 return typeof pathFill === 'string' && (pathFill).toLowerCase() === firstPathFill; 16708 }); 16709 }, 16710 16711 /** 16712 * Returns number representation of object's complexity 16713 * @return {Number} complexity 16714 */ 16715 complexity: function() { 16716 return this.paths.reduce(function(total, path) { 16717 return total + ((path && path.complexity) ? path.complexity() : 0); 16718 }, 0); 16719 }, 16720 16721 /** 16722 * Returns all paths in this path group 16723 * @return {Array} array of path objects included in this path group 16724 */ 16725 getObjects: function() { 16726 return this.paths; 16727 } 16728 }); 16729 16730 /** 16731 * Creates fabric.PathGroup instance from an object representation 16732 * @static 16733 * @memberOf fabric.PathGroup 16734 * @param {Object} object Object to create an instance from 16735 * @param {Function} callback Callback to invoke when an fabric.PathGroup instance is created 16736 */ 16737 fabric.PathGroup.fromObject = function(object, callback) { 16738 if (typeof object.paths === 'string') { 16739 fabric.loadSVGFromURL(object.paths, function (elements) { 16740 16741 var pathUrl = object.paths; 16742 delete object.paths; 16743 16744 var pathGroup = fabric.util.groupSVGElements(elements, object, pathUrl); 16745 16746 callback(pathGroup); 16747 }); 16748 } 16749 else { 16750 fabric.util.enlivenObjects(object.paths, function(enlivenedObjects) { 16751 delete object.paths; 16752 callback(new fabric.PathGroup(enlivenedObjects, object)); 16753 }); 16754 } 16755 }; 16756 16757 /** 16758 * Indicates that instances of this type are async 16759 * @static 16760 * @memberOf fabric.PathGroup 16761 * @type Boolean 16762 * @default 16763 */ 16764 fabric.PathGroup.async = true; 16765 16766 })(typeof exports !== 'undefined' ? exports : this); 16767 16768 16769 (function(global) { 16770 16771 'use strict'; 16772 16773 var fabric = global.fabric || (global.fabric = { }), 16774 extend = fabric.util.object.extend, 16775 min = fabric.util.array.min, 16776 max = fabric.util.array.max, 16777 invoke = fabric.util.array.invoke; 16778 16779 if (fabric.Group) { 16780 return; 16781 } 16782 16783 // lock-related properties, for use in fabric.Group#get 16784 // to enable locking behavior on group 16785 // when one of its objects has lock-related properties set 16786 var _lockProperties = { 16787 lockMovementX: true, 16788 lockMovementY: true, 16789 lockRotation: true, 16790 lockScalingX: true, 16791 lockScalingY: true, 16792 lockUniScaling: true 16793 }; 16794 16795 /** 16796 * Group class 16797 * @class fabric.Group 16798 * @extends fabric.Object 16799 * @mixes fabric.Collection 16800 * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#groups} 16801 * @see {@link fabric.Group#initialize} for constructor definition 16802 */ 16803 fabric.Group = fabric.util.createClass(fabric.Object, fabric.Collection, /** @lends fabric.Group.prototype */ { 16804 16805 /** 16806 * Type of an object 16807 * @type String 16808 * @default 16809 */ 16810 type: 'group', 16811 16812 /** 16813 * Width of stroke 16814 * @type Number 16815 * @default 16816 */ 16817 strokeWidth: 0, 16818 16819 /** 16820 * Constructor 16821 * @param {Object} objects Group objects 16822 * @param {Object} [options] Options object 16823 * @param {Boolean} [isAlreadyGrouped] if true, objects have been grouped already. 16824 * @return {Object} thisArg 16825 */ 16826 initialize: function(objects, options, isAlreadyGrouped) { 16827 options = options || { }; 16828 16829 this._objects = []; 16830 // if objects enclosed in a group have been grouped already, 16831 // we cannot change properties of objects. 16832 // Thus we need to set options to group without objects, 16833 // because delegatedProperties propagate to objects. 16834 isAlreadyGrouped && this.callSuper('initialize', options); 16835 16836 this._objects = objects || []; 16837 for (var i = this._objects.length; i--; ) { 16838 this._objects[i].group = this; 16839 } 16840 16841 this.originalState = { }; 16842 16843 if (options.originX) { 16844 this.originX = options.originX; 16845 } 16846 if (options.originY) { 16847 this.originY = options.originY; 16848 } 16849 16850 if (isAlreadyGrouped) { 16851 // do not change coordinate of objects enclosed in a group, 16852 // because objects coordinate system have been group coodinate system already. 16853 this._updateObjectsCoords(true); 16854 } 16855 else { 16856 this._calcBounds(); 16857 this._updateObjectsCoords(); 16858 this.callSuper('initialize', options); 16859 } 16860 16861 this.setCoords(); 16862 this.saveCoords(); 16863 }, 16864 16865 /** 16866 * @private 16867 * @param {Boolean} [skipCoordsChange] if true, coordinates of objects enclosed in a group do not change 16868 */ 16869 _updateObjectsCoords: function(skipCoordsChange) { 16870 for (var i = this._objects.length; i--; ){ 16871 this._updateObjectCoords(this._objects[i], skipCoordsChange); 16872 } 16873 }, 16874 16875 /** 16876 * @private 16877 * @param {Object} object 16878 * @param {Boolean} [skipCoordsChange] if true, coordinates of object dose not change 16879 */ 16880 _updateObjectCoords: function(object, skipCoordsChange) { 16881 // do not display corners of objects enclosed in a group 16882 object.__origHasControls = object.hasControls; 16883 object.hasControls = false; 16884 16885 if (skipCoordsChange) { 16886 return; 16887 } 16888 16889 var objectLeft = object.getLeft(), 16890 objectTop = object.getTop(), 16891 center = this.getCenterPoint(); 16892 16893 object.set({ 16894 originalLeft: objectLeft, 16895 originalTop: objectTop, 16896 left: objectLeft - center.x, 16897 top: objectTop - center.y 16898 }); 16899 object.setCoords(); 16900 }, 16901 16902 /** 16903 * Returns string represenation of a group 16904 * @return {String} 16905 */ 16906 toString: function() { 16907 return '#<fabric.Group: (' + this.complexity() + ')>'; 16908 }, 16909 16910 /** 16911 * Adds an object to a group; Then recalculates group's dimension, position. 16912 * @param {Object} object 16913 * @return {fabric.Group} thisArg 16914 * @chainable 16915 */ 16916 addWithUpdate: function(object) { 16917 this._restoreObjectsState(); 16918 fabric.util.resetObjectTransform(this); 16919 if (object) { 16920 this._objects.push(object); 16921 object.group = this; 16922 object._set('canvas', this.canvas); 16923 } 16924 // since _restoreObjectsState set objects inactive 16925 this.forEachObject(this._setObjectActive, this); 16926 this._calcBounds(); 16927 this._updateObjectsCoords(); 16928 return this; 16929 }, 16930 16931 /** 16932 * @private 16933 */ 16934 _setObjectActive: function(object) { 16935 object.set('active', true); 16936 object.group = this; 16937 }, 16938 16939 /** 16940 * Removes an object from a group; Then recalculates group's dimension, position. 16941 * @param {Object} object 16942 * @return {fabric.Group} thisArg 16943 * @chainable 16944 */ 16945 removeWithUpdate: function(object) { 16946 this._restoreObjectsState(); 16947 fabric.util.resetObjectTransform(this); 16948 // since _restoreObjectsState set objects inactive 16949 this.forEachObject(this._setObjectActive, this); 16950 16951 this.remove(object); 16952 this._calcBounds(); 16953 this._updateObjectsCoords(); 16954 16955 return this; 16956 }, 16957 16958 /** 16959 * @private 16960 */ 16961 _onObjectAdded: function(object) { 16962 object.group = this; 16963 object._set('canvas', this.canvas); 16964 }, 16965 16966 /** 16967 * @private 16968 */ 16969 _onObjectRemoved: function(object) { 16970 delete object.group; 16971 object.set('active', false); 16972 }, 16973 16974 /** 16975 * Properties that are delegated to group objects when reading/writing 16976 * @param {Object} delegatedProperties 16977 */ 16978 delegatedProperties: { 16979 fill: true, 16980 stroke: true, 16981 strokeWidth: true, 16982 fontFamily: true, 16983 fontWeight: true, 16984 fontSize: true, 16985 fontStyle: true, 16986 lineHeight: true, 16987 textDecoration: true, 16988 textAlign: true, 16989 backgroundColor: true 16990 }, 16991 16992 /** 16993 * @private 16994 */ 16995 _set: function(key, value) { 16996 var i = this._objects.length; 16997 16998 if (this.delegatedProperties[key] || key === 'canvas') { 16999 while (i--) { 17000 this._objects[i].set(key, value); 17001 } 17002 } 17003 else { 17004 while (i--) { 17005 this._objects[i].setOnGroup(key, value); 17006 } 17007 } 17008 17009 this.callSuper('_set', key, value); 17010 }, 17011 17012 /** 17013 * Returns object representation of an instance 17014 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 17015 * @return {Object} object representation of an instance 17016 */ 17017 toObject: function(propertiesToInclude) { 17018 return extend(this.callSuper('toObject', propertiesToInclude), { 17019 objects: invoke(this._objects, 'toObject', propertiesToInclude) 17020 }); 17021 }, 17022 17023 /** 17024 * Renders instance on a given context 17025 * @param {CanvasRenderingContext2D} ctx context to render instance on 17026 */ 17027 render: function(ctx) { 17028 // do not render if object is not visible 17029 if (!this.visible) { 17030 return; 17031 } 17032 17033 ctx.save(); 17034 if (this.transformMatrix) { 17035 ctx.transform.apply(ctx, this.transformMatrix); 17036 } 17037 this.transform(ctx); 17038 this._setShadow(ctx); 17039 this.clipTo && fabric.util.clipContext(this, ctx); 17040 // the array is now sorted in order of highest first, so start from end 17041 for (var i = 0, len = this._objects.length; i < len; i++) { 17042 this._renderObject(this._objects[i], ctx); 17043 } 17044 17045 this.clipTo && ctx.restore(); 17046 17047 ctx.restore(); 17048 }, 17049 17050 /** 17051 * Renders controls and borders for the object 17052 * @param {CanvasRenderingContext2D} ctx Context to render on 17053 * @param {Boolean} [noTransform] When true, context is not transformed 17054 */ 17055 _renderControls: function(ctx, noTransform) { 17056 this.callSuper('_renderControls', ctx, noTransform); 17057 for (var i = 0, len = this._objects.length; i < len; i++) { 17058 this._objects[i]._renderControls(ctx); 17059 } 17060 }, 17061 17062 /** 17063 * @private 17064 */ 17065 _renderObject: function(object, ctx) { 17066 // do not render if object is not visible 17067 if (!object.visible) { 17068 return; 17069 } 17070 17071 var originalHasRotatingPoint = object.hasRotatingPoint; 17072 object.hasRotatingPoint = false; 17073 object.render(ctx); 17074 object.hasRotatingPoint = originalHasRotatingPoint; 17075 }, 17076 17077 /** 17078 * Retores original state of each of group objects (original state is that which was before group was created). 17079 * @private 17080 * @return {fabric.Group} thisArg 17081 * @chainable 17082 */ 17083 _restoreObjectsState: function() { 17084 this._objects.forEach(this._restoreObjectState, this); 17085 return this; 17086 }, 17087 17088 /** 17089 * Realises the transform from this group onto the supplied object 17090 * i.e. it tells you what would happen if the supplied object was in 17091 * the group, and then the group was destroyed. It mutates the supplied 17092 * object. 17093 * @param {fabric.Object} object 17094 * @return {fabric.Object} transformedObject 17095 */ 17096 realizeTransform: function(object) { 17097 var matrix = object.calcTransformMatrix(), 17098 options = fabric.util.qrDecompose(matrix), 17099 center = new fabric.Point(options.translateX, options.translateY); 17100 object.scaleX = options.scaleX; 17101 object.scaleY = options.scaleY; 17102 object.skewX = options.skewX; 17103 object.skewY = options.skewY; 17104 object.angle = options.angle; 17105 object.flipX = false; 17106 object.flipY = false; 17107 object.setPositionByOrigin(center, 'center', 'center'); 17108 return object; 17109 }, 17110 17111 /** 17112 * Restores original state of a specified object in group 17113 * @private 17114 * @param {fabric.Object} object 17115 * @return {fabric.Group} thisArg 17116 */ 17117 _restoreObjectState: function(object) { 17118 this.realizeTransform(object); 17119 object.setCoords(); 17120 object.hasControls = object.__origHasControls; 17121 delete object.__origHasControls; 17122 object.set('active', false); 17123 delete object.group; 17124 17125 return this; 17126 }, 17127 17128 /** 17129 * Destroys a group (restoring state of its objects) 17130 * @return {fabric.Group} thisArg 17131 * @chainable 17132 */ 17133 destroy: function() { 17134 return this._restoreObjectsState(); 17135 }, 17136 17137 /** 17138 * Saves coordinates of this instance (to be used together with `hasMoved`) 17139 * @saveCoords 17140 * @return {fabric.Group} thisArg 17141 * @chainable 17142 */ 17143 saveCoords: function() { 17144 this._originalLeft = this.get('left'); 17145 this._originalTop = this.get('top'); 17146 return this; 17147 }, 17148 17149 /** 17150 * Checks whether this group was moved (since `saveCoords` was called last) 17151 * @return {Boolean} true if an object was moved (since fabric.Group#saveCoords was called) 17152 */ 17153 hasMoved: function() { 17154 return this._originalLeft !== this.get('left') || 17155 this._originalTop !== this.get('top'); 17156 }, 17157 17158 /** 17159 * Sets coordinates of all group objects 17160 * @return {fabric.Group} thisArg 17161 * @chainable 17162 */ 17163 setObjectsCoords: function() { 17164 this.forEachObject(function(object) { 17165 object.setCoords(); 17166 }); 17167 return this; 17168 }, 17169 17170 /** 17171 * @private 17172 */ 17173 _calcBounds: function(onlyWidthHeight) { 17174 var aX = [], 17175 aY = [], 17176 o, prop, 17177 props = ['tr', 'br', 'bl', 'tl'], 17178 i = 0, iLen = this._objects.length, 17179 j, jLen = props.length; 17180 17181 for ( ; i < iLen; ++i) { 17182 o = this._objects[i]; 17183 o.setCoords(); 17184 for (j = 0; j < jLen; j++) { 17185 prop = props[j]; 17186 aX.push(o.oCoords[prop].x); 17187 aY.push(o.oCoords[prop].y); 17188 } 17189 } 17190 17191 this.set(this._getBounds(aX, aY, onlyWidthHeight)); 17192 }, 17193 17194 /** 17195 * @private 17196 */ 17197 _getBounds: function(aX, aY, onlyWidthHeight) { 17198 var ivt = fabric.util.invertTransform(this.getViewportTransform()), 17199 minXY = fabric.util.transformPoint(new fabric.Point(min(aX), min(aY)), ivt), 17200 maxXY = fabric.util.transformPoint(new fabric.Point(max(aX), max(aY)), ivt), 17201 obj = { 17202 width: (maxXY.x - minXY.x) || 0, 17203 height: (maxXY.y - minXY.y) || 0 17204 }; 17205 17206 if (!onlyWidthHeight) { 17207 obj.left = minXY.x || 0; 17208 obj.top = minXY.y || 0; 17209 if (this.originX === 'center') { 17210 obj.left += obj.width / 2; 17211 } 17212 if (this.originX === 'right') { 17213 obj.left += obj.width; 17214 } 17215 if (this.originY === 'center') { 17216 obj.top += obj.height / 2; 17217 } 17218 if (this.originY === 'bottom') { 17219 obj.top += obj.height; 17220 } 17221 } 17222 return obj; 17223 }, 17224 17225 /* _TO_SVG_START_ */ 17226 /** 17227 * Returns svg representation of an instance 17228 * @param {Function} [reviver] Method for further parsing of svg representation. 17229 * @return {String} svg representation of an instance 17230 */ 17231 toSVG: function(reviver) { 17232 var markup = this._createBaseSVGMarkup(); 17233 markup.push( 17234 '<g transform="', 17235 /* avoiding styles intentionally */ 17236 this.getSvgTransform(), 17237 this.getSvgTransformMatrix(), 17238 '" style="', 17239 this.getSvgFilter(), 17240 '">\n' 17241 ); 17242 17243 for (var i = 0, len = this._objects.length; i < len; i++) { 17244 markup.push('\t', this._objects[i].toSVG(reviver)); 17245 } 17246 17247 markup.push('</g>\n'); 17248 17249 return reviver ? reviver(markup.join('')) : markup.join(''); 17250 }, 17251 /* _TO_SVG_END_ */ 17252 17253 /** 17254 * Returns requested property 17255 * @param {String} prop Property to get 17256 * @return {Any} 17257 */ 17258 get: function(prop) { 17259 if (prop in _lockProperties) { 17260 if (this[prop]) { 17261 return this[prop]; 17262 } 17263 else { 17264 for (var i = 0, len = this._objects.length; i < len; i++) { 17265 if (this._objects[i][prop]) { 17266 return true; 17267 } 17268 } 17269 return false; 17270 } 17271 } 17272 else { 17273 if (prop in this.delegatedProperties) { 17274 return this._objects[0] && this._objects[0].get(prop); 17275 } 17276 return this[prop]; 17277 } 17278 } 17279 }); 17280 17281 /** 17282 * Returns {@link fabric.Group} instance from an object representation 17283 * @static 17284 * @memberOf fabric.Group 17285 * @param {Object} object Object to create a group from 17286 * @param {Function} [callback] Callback to invoke when an group instance is created 17287 * @return {fabric.Group} An instance of fabric.Group 17288 */ 17289 fabric.Group.fromObject = function(object, callback) { 17290 fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) { 17291 delete object.objects; 17292 callback && callback(new fabric.Group(enlivenedObjects, object, true)); 17293 }); 17294 }; 17295 17296 /** 17297 * Indicates that instances of this type are async 17298 * @static 17299 * @memberOf fabric.Group 17300 * @type Boolean 17301 * @default 17302 */ 17303 fabric.Group.async = true; 17304 17305 })(typeof exports !== 'undefined' ? exports : this); 17306 17307 17308 (function(global) { 17309 17310 'use strict'; 17311 17312 var extend = fabric.util.object.extend; 17313 17314 if (!global.fabric) { 17315 global.fabric = { }; 17316 } 17317 17318 if (global.fabric.Image) { 17319 fabric.warn('fabric.Image is already defined.'); 17320 return; 17321 } 17322 17323 /** 17324 * Image class 17325 * @class fabric.Image 17326 * @extends fabric.Object 17327 * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#images} 17328 * @see {@link fabric.Image#initialize} for constructor definition 17329 */ 17330 fabric.Image = fabric.util.createClass(fabric.Object, /** @lends fabric.Image.prototype */ { 17331 17332 /** 17333 * Type of an object 17334 * @type String 17335 * @default 17336 */ 17337 type: 'image', 17338 17339 /** 17340 * crossOrigin value (one of "", "anonymous", "use-credentials") 17341 * @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes 17342 * @type String 17343 * @default 17344 */ 17345 crossOrigin: '', 17346 17347 /** 17348 * AlignX value, part of preserveAspectRatio (one of "none", "mid", "min", "max") 17349 * @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute 17350 * This parameter defines how the picture is aligned to its viewport when image element width differs from image width. 17351 * @type String 17352 * @default 17353 */ 17354 alignX: 'none', 17355 17356 /** 17357 * AlignY value, part of preserveAspectRatio (one of "none", "mid", "min", "max") 17358 * @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute 17359 * This parameter defines how the picture is aligned to its viewport when image element height differs from image height. 17360 * @type String 17361 * @default 17362 */ 17363 alignY: 'none', 17364 17365 /** 17366 * meetOrSlice value, part of preserveAspectRatio (one of "meet", "slice"). 17367 * if meet the image is always fully visibile, if slice the viewport is always filled with image. 17368 * @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute 17369 * @type String 17370 * @default 17371 */ 17372 meetOrSlice: 'meet', 17373 17374 /** 17375 * Width of a stroke. 17376 * For image quality a stroke multiple of 2 gives better results. 17377 * @type Number 17378 * @default 17379 */ 17380 strokeWidth: 0, 17381 17382 /** 17383 * private 17384 * contains last value of scaleX to detect 17385 * if the Image got resized after the last Render 17386 * @type Number 17387 */ 17388 _lastScaleX: 1, 17389 17390 /** 17391 * private 17392 * contains last value of scaleY to detect 17393 * if the Image got resized after the last Render 17394 * @type Number 17395 */ 17396 _lastScaleY: 1, 17397 17398 /** 17399 * Constructor 17400 * @param {HTMLImageElement | String} element Image element 17401 * @param {Object} [options] Options object 17402 * @return {fabric.Image} thisArg 17403 */ 17404 initialize: function(element, options) { 17405 options || (options = { }); 17406 this.filters = [ ]; 17407 this.resizeFilters = [ ]; 17408 this.callSuper('initialize', options); 17409 this._initElement(element, options); 17410 }, 17411 17412 /** 17413 * Returns image element which this instance if based on 17414 * @return {HTMLImageElement} Image element 17415 */ 17416 getElement: function() { 17417 return this._element; 17418 }, 17419 17420 /** 17421 * Sets image element for this instance to a specified one. 17422 * If filters defined they are applied to new image. 17423 * You might need to call `canvas.renderAll` and `object.setCoords` after replacing, to render new image and update controls area. 17424 * @param {HTMLImageElement} element 17425 * @param {Function} [callback] Callback is invoked when all filters have been applied and new image is generated 17426 * @param {Object} [options] Options object 17427 * @return {fabric.Image} thisArg 17428 * @chainable 17429 */ 17430 setElement: function(element, callback, options) { 17431 this._element = element; 17432 this._originalElement = element; 17433 this._initConfig(options); 17434 17435 if (this.filters.length !== 0) { 17436 this.applyFilters(callback); 17437 } 17438 else if (callback) { 17439 callback(); 17440 } 17441 17442 return this; 17443 }, 17444 17445 /** 17446 * Sets crossOrigin value (on an instance and corresponding image element) 17447 * @return {fabric.Image} thisArg 17448 * @chainable 17449 */ 17450 setCrossOrigin: function(value) { 17451 this.crossOrigin = value; 17452 this._element.crossOrigin = value; 17453 17454 return this; 17455 }, 17456 17457 /** 17458 * Returns original size of an image 17459 * @return {Object} Object with "width" and "height" properties 17460 */ 17461 getOriginalSize: function() { 17462 var element = this.getElement(); 17463 return { 17464 width: element.width, 17465 height: element.height 17466 }; 17467 }, 17468 17469 /** 17470 * @private 17471 * @param {CanvasRenderingContext2D} ctx Context to render on 17472 */ 17473 _stroke: function(ctx) { 17474 ctx.save(); 17475 this._setStrokeStyles(ctx); 17476 ctx.beginPath(); 17477 ctx.strokeRect(-this.width / 2, -this.height / 2, this.width, this.height); 17478 ctx.closePath(); 17479 ctx.restore(); 17480 }, 17481 17482 /** 17483 * @private 17484 * @param {CanvasRenderingContext2D} ctx Context to render on 17485 */ 17486 _renderDashedStroke: function(ctx) { 17487 var x = -this.width / 2, 17488 y = -this.height / 2, 17489 w = this.width, 17490 h = this.height; 17491 17492 ctx.save(); 17493 this._setStrokeStyles(ctx); 17494 17495 ctx.beginPath(); 17496 fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray); 17497 fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray); 17498 fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray); 17499 fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray); 17500 ctx.closePath(); 17501 ctx.restore(); 17502 }, 17503 17504 /** 17505 * Returns object representation of an instance 17506 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 17507 * @return {Object} Object representation of an instance 17508 */ 17509 toObject: function(propertiesToInclude) { 17510 var filters = [ ]; 17511 this.filters.forEach(function(filterObj) { 17512 if (filterObj) { 17513 filters.push(filterObj.toObject()); 17514 } 17515 }); 17516 var object = extend(this.callSuper('toObject', propertiesToInclude), { 17517 src: this._originalElement.src || this._originalElement._src, 17518 filters: filters, 17519 crossOrigin: this.crossOrigin, 17520 alignX: this.alignX, 17521 alignY: this.alignY, 17522 meetOrSlice: this.meetOrSlice 17523 }); 17524 17525 if (this.resizeFilters.length > 0) { 17526 object.resizeFilters = this.resizeFilters.map(function(filterObj) { 17527 return filterObj && filterObj.toObject(); 17528 }); 17529 } 17530 17531 if (!this.includeDefaultValues) { 17532 this._removeDefaultValues(object); 17533 } 17534 17535 return object; 17536 }, 17537 17538 /* _TO_SVG_START_ */ 17539 /** 17540 * Returns SVG representation of an instance 17541 * @param {Function} [reviver] Method for further parsing of svg representation. 17542 * @return {String} svg representation of an instance 17543 */ 17544 toSVG: function(reviver) { 17545 var markup = this._createBaseSVGMarkup(), x = -this.width / 2, y = -this.height / 2, 17546 preserveAspectRatio = 'none'; 17547 if (this.group && this.group.type === 'path-group') { 17548 x = this.left; 17549 y = this.top; 17550 } 17551 if (this.alignX !== 'none' && this.alignY !== 'none') { 17552 preserveAspectRatio = 'x' + this.alignX + 'Y' + this.alignY + ' ' + this.meetOrSlice; 17553 } 17554 markup.push( 17555 '<g transform="', this.getSvgTransform(), this.getSvgTransformMatrix(), '">\n', 17556 '<image xlink:href="', this.getSvgSrc(), 17557 '" x="', x, '" y="', y, 17558 '" style="', this.getSvgStyles(), 17559 // we're essentially moving origin of transformation from top/left corner to the center of the shape 17560 // by wrapping it in container <g> element with actual transformation, then offsetting object to the top/left 17561 // so that object's center aligns with container's left/top 17562 '" width="', this.width, 17563 '" height="', this.height, 17564 '" preserveAspectRatio="', preserveAspectRatio, '"', 17565 '></image>\n' 17566 ); 17567 17568 if (this.stroke || this.strokeDashArray) { 17569 var origFill = this.fill; 17570 this.fill = null; 17571 markup.push( 17572 '<rect ', 17573 'x="', x, '" y="', y, 17574 '" width="', this.width, '" height="', this.height, 17575 '" style="', this.getSvgStyles(), 17576 '"/>\n' 17577 ); 17578 this.fill = origFill; 17579 } 17580 17581 markup.push('</g>\n'); 17582 17583 return reviver ? reviver(markup.join('')) : markup.join(''); 17584 }, 17585 /* _TO_SVG_END_ */ 17586 17587 /** 17588 * Returns source of an image 17589 * @return {String} Source of an image 17590 */ 17591 getSrc: function() { 17592 if (this.getElement()) { 17593 return this.getElement().src || this.getElement()._src; 17594 } 17595 }, 17596 17597 /** 17598 * Sets source of an image 17599 * @param {String} src Source string (URL) 17600 * @param {Function} [callback] Callback is invoked when image has been loaded (and all filters have been applied) 17601 * @param {Object} [options] Options object 17602 * @return {fabric.Image} thisArg 17603 * @chainable 17604 */ 17605 setSrc: function(src, callback, options) { 17606 fabric.util.loadImage(src, function(img) { 17607 return this.setElement(img, callback, options); 17608 }, this, options && options.crossOrigin); 17609 }, 17610 17611 /** 17612 * Returns string representation of an instance 17613 * @return {String} String representation of an instance 17614 */ 17615 toString: function() { 17616 return '#<fabric.Image: { src: "' + this.getSrc() + '" }>'; 17617 }, 17618 17619 /** 17620 * Returns a clone of an instance 17621 * @param {Function} callback Callback is invoked with a clone as a first argument 17622 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 17623 */ 17624 clone: function(callback, propertiesToInclude) { 17625 this.constructor.fromObject(this.toObject(propertiesToInclude), callback); 17626 }, 17627 17628 /** 17629 * Applies filters assigned to this image (from "filters" array) 17630 * @method applyFilters 17631 * @param {Function} callback Callback is invoked when all filters have been applied and new image is generated 17632 * @return {fabric.Image} thisArg 17633 * @chainable 17634 */ 17635 applyFilters: function(callback, filters, imgElement, forResizing) { 17636 17637 filters = filters || this.filters; 17638 imgElement = imgElement || this._originalElement; 17639 17640 if (!imgElement) { 17641 return; 17642 } 17643 17644 var imgEl = imgElement, 17645 canvasEl = fabric.util.createCanvasElement(), 17646 replacement = fabric.util.createImage(), 17647 _this = this; 17648 17649 canvasEl.width = imgEl.width; 17650 canvasEl.height = imgEl.height; 17651 canvasEl.getContext('2d').drawImage(imgEl, 0, 0, imgEl.width, imgEl.height); 17652 17653 if (filters.length === 0) { 17654 this._element = imgElement; 17655 callback && callback(); 17656 return canvasEl; 17657 } 17658 filters.forEach(function(filter) { 17659 filter && filter.applyTo(canvasEl, filter.scaleX || _this.scaleX, filter.scaleY || _this.scaleY); 17660 if (!forResizing && filter && filter.type === 'Resize') { 17661 _this.width *= filter.scaleX; 17662 _this.height *= filter.scaleY; 17663 } 17664 }); 17665 17666 /** @ignore */ 17667 replacement.width = canvasEl.width; 17668 replacement.height = canvasEl.height; 17669 17670 if (fabric.isLikelyNode) { 17671 replacement.src = canvasEl.toBuffer(undefined, fabric.Image.pngCompression); 17672 // onload doesn't fire in some node versions, so we invoke callback manually 17673 _this._element = replacement; 17674 !forResizing && (_this._filteredEl = replacement); 17675 callback && callback(); 17676 } 17677 else { 17678 replacement.onload = function() { 17679 _this._element = replacement; 17680 !forResizing && (_this._filteredEl = replacement); 17681 callback && callback(); 17682 replacement.onload = canvasEl = imgEl = null; 17683 }; 17684 replacement.src = canvasEl.toDataURL('image/png'); 17685 } 17686 return canvasEl; 17687 }, 17688 17689 /** 17690 * @private 17691 * @param {CanvasRenderingContext2D} ctx Context to render on 17692 */ 17693 _render: function(ctx, noTransform) { 17694 var x, y, imageMargins = this._findMargins(), elementToDraw; 17695 17696 x = (noTransform ? this.left : -this.width / 2); 17697 y = (noTransform ? this.top : -this.height / 2); 17698 17699 if (this.meetOrSlice === 'slice') { 17700 ctx.beginPath(); 17701 ctx.rect(x, y, this.width, this.height); 17702 ctx.clip(); 17703 } 17704 17705 if (this.isMoving === false && this.resizeFilters.length && this._needsResize()) { 17706 this._lastScaleX = this.scaleX; 17707 this._lastScaleY = this.scaleY; 17708 elementToDraw = this.applyFilters(null, this.resizeFilters, this._filteredEl || this._originalElement, true); 17709 } 17710 else { 17711 elementToDraw = this._element; 17712 } 17713 elementToDraw && ctx.drawImage(elementToDraw, 17714 x + imageMargins.marginX, 17715 y + imageMargins.marginY, 17716 imageMargins.width, 17717 imageMargins.height 17718 ); 17719 17720 this._renderStroke(ctx); 17721 }, 17722 17723 /** 17724 * @private, needed to check if image needs resize 17725 */ 17726 _needsResize: function() { 17727 return (this.scaleX !== this._lastScaleX || this.scaleY !== this._lastScaleY); 17728 }, 17729 17730 /** 17731 * @private 17732 */ 17733 _findMargins: function() { 17734 var width = this.width, height = this.height, scales, 17735 scale, marginX = 0, marginY = 0; 17736 17737 if (this.alignX !== 'none' || this.alignY !== 'none') { 17738 scales = [this.width / this._element.width, this.height / this._element.height]; 17739 scale = this.meetOrSlice === 'meet' 17740 ? Math.min.apply(null, scales) : Math.max.apply(null, scales); 17741 width = this._element.width * scale; 17742 height = this._element.height * scale; 17743 if (this.alignX === 'Mid') { 17744 marginX = (this.width - width) / 2; 17745 } 17746 if (this.alignX === 'Max') { 17747 marginX = this.width - width; 17748 } 17749 if (this.alignY === 'Mid') { 17750 marginY = (this.height - height) / 2; 17751 } 17752 if (this.alignY === 'Max') { 17753 marginY = this.height - height; 17754 } 17755 } 17756 return { 17757 width: width, 17758 height: height, 17759 marginX: marginX, 17760 marginY: marginY 17761 }; 17762 }, 17763 17764 /** 17765 * @private 17766 */ 17767 _resetWidthHeight: function() { 17768 var element = this.getElement(); 17769 17770 this.set('width', element.width); 17771 this.set('height', element.height); 17772 }, 17773 17774 /** 17775 * The Image class's initialization method. This method is automatically 17776 * called by the constructor. 17777 * @private 17778 * @param {HTMLImageElement|String} element The element representing the image 17779 * @param {Object} [options] Options object 17780 */ 17781 _initElement: function(element, options) { 17782 this.setElement(fabric.util.getById(element), null, options); 17783 fabric.util.addClass(this.getElement(), fabric.Image.CSS_CANVAS); 17784 }, 17785 17786 /** 17787 * @private 17788 * @param {Object} [options] Options object 17789 */ 17790 _initConfig: function(options) { 17791 options || (options = { }); 17792 this.setOptions(options); 17793 this._setWidthHeight(options); 17794 if (this._element && this.crossOrigin) { 17795 this._element.crossOrigin = this.crossOrigin; 17796 } 17797 }, 17798 17799 /** 17800 * @private 17801 * @param {Array} filters to be initialized 17802 * @param {Function} callback Callback to invoke when all fabric.Image.filters instances are created 17803 */ 17804 _initFilters: function(filters, callback) { 17805 if (filters && filters.length) { 17806 fabric.util.enlivenObjects(filters, function(enlivenedObjects) { 17807 callback && callback(enlivenedObjects); 17808 }, 'fabric.Image.filters'); 17809 } 17810 else { 17811 callback && callback(); 17812 } 17813 }, 17814 17815 /** 17816 * @private 17817 * @param {Object} [options] Object with width/height properties 17818 */ 17819 _setWidthHeight: function(options) { 17820 this.width = 'width' in options 17821 ? options.width 17822 : (this.getElement() 17823 ? this.getElement().width || 0 17824 : 0); 17825 17826 this.height = 'height' in options 17827 ? options.height 17828 : (this.getElement() 17829 ? this.getElement().height || 0 17830 : 0); 17831 }, 17832 17833 /** 17834 * Returns complexity of an instance 17835 * @return {Number} complexity of this instance 17836 */ 17837 complexity: function() { 17838 return 1; 17839 } 17840 }); 17841 17842 /** 17843 * Default CSS class name for canvas 17844 * @static 17845 * @type String 17846 * @default 17847 */ 17848 fabric.Image.CSS_CANVAS = 'canvas-img'; 17849 17850 /** 17851 * Alias for getSrc 17852 * @static 17853 */ 17854 fabric.Image.prototype.getSvgSrc = fabric.Image.prototype.getSrc; 17855 17856 /** 17857 * Creates an instance of fabric.Image from its object representation 17858 * @static 17859 * @param {Object} object Object to create an instance from 17860 * @param {Function} [callback] Callback to invoke when an image instance is created 17861 */ 17862 fabric.Image.fromObject = function(object, callback) { 17863 fabric.util.loadImage(object.src, function(img) { 17864 fabric.Image.prototype._initFilters.call(object, object.filters, function(filters) { 17865 object.filters = filters || [ ]; 17866 fabric.Image.prototype._initFilters.call(object, object.resizeFilters, function(resizeFilters) { 17867 object.resizeFilters = resizeFilters || [ ]; 17868 var instance = new fabric.Image(img, object); 17869 callback && callback(instance); 17870 }); 17871 }); 17872 }, null, object.crossOrigin); 17873 }; 17874 17875 /** 17876 * Creates an instance of fabric.Image from an URL string 17877 * @static 17878 * @param {String} url URL to create an image from 17879 * @param {Function} [callback] Callback to invoke when image is created (newly created image is passed as a first argument) 17880 * @param {Object} [imgOptions] Options object 17881 */ 17882 fabric.Image.fromURL = function(url, callback, imgOptions) { 17883 fabric.util.loadImage(url, function(img) { 17884 callback && callback(new fabric.Image(img, imgOptions)); 17885 }, null, imgOptions && imgOptions.crossOrigin); 17886 }; 17887 17888 /* _FROM_SVG_START_ */ 17889 /** 17890 * List of attribute names to account for when parsing SVG element (used by {@link fabric.Image.fromElement}) 17891 * @static 17892 * @see {@link http://www.w3.org/TR/SVG/struct.html#ImageElement} 17893 */ 17894 fabric.Image.ATTRIBUTE_NAMES = 17895 fabric.SHARED_ATTRIBUTES.concat('x y width height preserveAspectRatio xlink:href'.split(' ')); 17896 17897 /** 17898 * Returns {@link fabric.Image} instance from an SVG element 17899 * @static 17900 * @param {SVGElement} element Element to parse 17901 * @param {Function} callback Callback to execute when fabric.Image object is created 17902 * @param {Object} [options] Options object 17903 * @return {fabric.Image} Instance of fabric.Image 17904 */ 17905 fabric.Image.fromElement = function(element, callback, options) { 17906 var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES), 17907 preserveAR; 17908 17909 if (parsedAttributes.preserveAspectRatio) { 17910 preserveAR = fabric.util.parsePreserveAspectRatioAttribute(parsedAttributes.preserveAspectRatio); 17911 extend(parsedAttributes, preserveAR); 17912 } 17913 17914 fabric.Image.fromURL(parsedAttributes['xlink:href'], callback, 17915 extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes)); 17916 }; 17917 /* _FROM_SVG_END_ */ 17918 17919 /** 17920 * Indicates that instances of this type are async 17921 * @static 17922 * @type Boolean 17923 * @default 17924 */ 17925 fabric.Image.async = true; 17926 17927 /** 17928 * Indicates compression level used when generating PNG under Node (in applyFilters). Any of 0-9 17929 * @static 17930 * @type Number 17931 * @default 17932 */ 17933 fabric.Image.pngCompression = 1; 17934 17935 })(typeof exports !== 'undefined' ? exports : this); 17936 17937 17938 fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { 17939 17940 /** 17941 * @private 17942 * @return {Number} angle value 17943 */ 17944 _getAngleValueForStraighten: function() { 17945 var angle = this.getAngle() % 360; 17946 if (angle > 0) { 17947 return Math.round((angle - 1) / 90) * 90; 17948 } 17949 return Math.round(angle / 90) * 90; 17950 }, 17951 17952 /** 17953 * Straightens an object (rotating it from current angle to one of 0, 90, 180, 270, etc. depending on which is closer) 17954 * @return {fabric.Object} thisArg 17955 * @chainable 17956 */ 17957 straighten: function() { 17958 this.setAngle(this._getAngleValueForStraighten()); 17959 return this; 17960 }, 17961 17962 /** 17963 * Same as {@link fabric.Object.prototype.straighten} but with animation 17964 * @param {Object} callbacks Object with callback functions 17965 * @param {Function} [callbacks.onComplete] Invoked on completion 17966 * @param {Function} [callbacks.onChange] Invoked on every step of animation 17967 * @return {fabric.Object} thisArg 17968 * @chainable 17969 */ 17970 fxStraighten: function(callbacks) { 17971 callbacks = callbacks || { }; 17972 17973 var empty = function() { }, 17974 onComplete = callbacks.onComplete || empty, 17975 onChange = callbacks.onChange || empty, 17976 _this = this; 17977 17978 fabric.util.animate({ 17979 startValue: this.get('angle'), 17980 endValue: this._getAngleValueForStraighten(), 17981 duration: this.FX_DURATION, 17982 onChange: function(value) { 17983 _this.setAngle(value); 17984 onChange(); 17985 }, 17986 onComplete: function() { 17987 _this.setCoords(); 17988 onComplete(); 17989 }, 17990 onStart: function() { 17991 _this.set('active', false); 17992 } 17993 }); 17994 17995 return this; 17996 } 17997 }); 17998 17999 fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { 18000 18001 /** 18002 * Straightens object, then rerenders canvas 18003 * @param {fabric.Object} object Object to straighten 18004 * @return {fabric.Canvas} thisArg 18005 * @chainable 18006 */ 18007 straightenObject: function (object) { 18008 object.straighten(); 18009 this.renderAll(); 18010 return this; 18011 }, 18012 18013 /** 18014 * Same as {@link fabric.Canvas.prototype.straightenObject}, but animated 18015 * @param {fabric.Object} object Object to straighten 18016 * @return {fabric.Canvas} thisArg 18017 * @chainable 18018 */ 18019 fxStraightenObject: function (object) { 18020 object.fxStraighten({ 18021 onChange: this.renderAll.bind(this) 18022 }); 18023 return this; 18024 } 18025 }); 18026 18027 18028 /** 18029 * @namespace fabric.Image.filters 18030 * @memberOf fabric.Image 18031 * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#image_filters} 18032 * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} 18033 */ 18034 fabric.Image.filters = fabric.Image.filters || { }; 18035 18036 /** 18037 * Root filter class from which all filter classes inherit from 18038 * @class fabric.Image.filters.BaseFilter 18039 * @memberOf fabric.Image.filters 18040 */ 18041 fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Image.filters.BaseFilter.prototype */ { 18042 18043 /** 18044 * Filter type 18045 * @param {String} type 18046 * @default 18047 */ 18048 type: 'BaseFilter', 18049 18050 /** 18051 * Constructor 18052 * @param {Object} [options] Options object 18053 */ 18054 initialize: function(options) { 18055 if (options) { 18056 this.setOptions(options); 18057 } 18058 }, 18059 18060 /** 18061 * Sets filter's properties from options 18062 * @param {Object} [options] Options object 18063 */ 18064 setOptions: function(options) { 18065 for (var prop in options) { 18066 this[prop] = options[prop]; 18067 } 18068 }, 18069 18070 /** 18071 * Returns object representation of an instance 18072 * @return {Object} Object representation of an instance 18073 */ 18074 toObject: function() { 18075 return { type: this.type }; 18076 }, 18077 18078 /** 18079 * Returns a JSON representation of an instance 18080 * @return {Object} JSON 18081 */ 18082 toJSON: function() { 18083 // delegate, not alias 18084 return this.toObject(); 18085 } 18086 }); 18087 18088 18089 (function(global) { 18090 18091 'use strict'; 18092 18093 var fabric = global.fabric || (global.fabric = { }), 18094 extend = fabric.util.object.extend; 18095 18096 /** 18097 * Brightness filter class 18098 * @class fabric.Image.filters.Brightness 18099 * @memberOf fabric.Image.filters 18100 * @extends fabric.Image.filters.BaseFilter 18101 * @see {@link fabric.Image.filters.Brightness#initialize} for constructor definition 18102 * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} 18103 * @example 18104 * var filter = new fabric.Image.filters.Brightness({ 18105 * brightness: 200 18106 * }); 18107 * object.filters.push(filter); 18108 * object.applyFilters(canvas.renderAll.bind(canvas)); 18109 */ 18110 fabric.Image.filters.Brightness = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Brightness.prototype */ { 18111 18112 /** 18113 * Filter type 18114 * @param {String} type 18115 * @default 18116 */ 18117 type: 'Brightness', 18118 18119 /** 18120 * Constructor 18121 * @memberOf fabric.Image.filters.Brightness.prototype 18122 * @param {Object} [options] Options object 18123 * @param {Number} [options.brightness=0] Value to brighten the image up (0..255) 18124 */ 18125 initialize: function(options) { 18126 options = options || { }; 18127 this.brightness = options.brightness || 0; 18128 }, 18129 18130 /** 18131 * Applies filter to canvas element 18132 * @param {Object} canvasEl Canvas element to apply filter to 18133 */ 18134 applyTo: function(canvasEl) { 18135 var context = canvasEl.getContext('2d'), 18136 imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), 18137 data = imageData.data, 18138 brightness = this.brightness; 18139 18140 for (var i = 0, len = data.length; i < len; i += 4) { 18141 data[i] += brightness; 18142 data[i + 1] += brightness; 18143 data[i + 2] += brightness; 18144 } 18145 18146 context.putImageData(imageData, 0, 0); 18147 }, 18148 18149 /** 18150 * Returns object representation of an instance 18151 * @return {Object} Object representation of an instance 18152 */ 18153 toObject: function() { 18154 return extend(this.callSuper('toObject'), { 18155 brightness: this.brightness 18156 }); 18157 } 18158 }); 18159 18160 /** 18161 * Returns filter instance from an object representation 18162 * @static 18163 * @param {Object} object Object to create an instance from 18164 * @return {fabric.Image.filters.Brightness} Instance of fabric.Image.filters.Brightness 18165 */ 18166 fabric.Image.filters.Brightness.fromObject = function(object) { 18167 return new fabric.Image.filters.Brightness(object); 18168 }; 18169 18170 })(typeof exports !== 'undefined' ? exports : this); 18171 18172 18173 (function(global) { 18174 18175 'use strict'; 18176 18177 var fabric = global.fabric || (global.fabric = { }), 18178 extend = fabric.util.object.extend; 18179 18180 /** 18181 * Adapted from <a href="http://www.html5rocks.com/en/tutorials/canvas/imagefilters/">html5rocks article</a> 18182 * @class fabric.Image.filters.Convolute 18183 * @memberOf fabric.Image.filters 18184 * @extends fabric.Image.filters.BaseFilter 18185 * @see {@link fabric.Image.filters.Convolute#initialize} for constructor definition 18186 * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} 18187 * @example <caption>Sharpen filter</caption> 18188 * var filter = new fabric.Image.filters.Convolute({ 18189 * matrix: [ 0, -1, 0, 18190 * -1, 5, -1, 18191 * 0, -1, 0 ] 18192 * }); 18193 * object.filters.push(filter); 18194 * object.applyFilters(canvas.renderAll.bind(canvas)); 18195 * @example <caption>Blur filter</caption> 18196 * var filter = new fabric.Image.filters.Convolute({ 18197 * matrix: [ 1/9, 1/9, 1/9, 18198 * 1/9, 1/9, 1/9, 18199 * 1/9, 1/9, 1/9 ] 18200 * }); 18201 * object.filters.push(filter); 18202 * object.applyFilters(canvas.renderAll.bind(canvas)); 18203 * @example <caption>Emboss filter</caption> 18204 * var filter = new fabric.Image.filters.Convolute({ 18205 * matrix: [ 1, 1, 1, 18206 * 1, 0.7, -1, 18207 * -1, -1, -1 ] 18208 * }); 18209 * object.filters.push(filter); 18210 * object.applyFilters(canvas.renderAll.bind(canvas)); 18211 * @example <caption>Emboss filter with opaqueness</caption> 18212 * var filter = new fabric.Image.filters.Convolute({ 18213 * opaque: true, 18214 * matrix: [ 1, 1, 1, 18215 * 1, 0.7, -1, 18216 * -1, -1, -1 ] 18217 * }); 18218 * object.filters.push(filter); 18219 * object.applyFilters(canvas.renderAll.bind(canvas)); 18220 */ 18221 fabric.Image.filters.Convolute = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Convolute.prototype */ { 18222 18223 /** 18224 * Filter type 18225 * @param {String} type 18226 * @default 18227 */ 18228 type: 'Convolute', 18229 18230 /** 18231 * Constructor 18232 * @memberOf fabric.Image.filters.Convolute.prototype 18233 * @param {Object} [options] Options object 18234 * @param {Boolean} [options.opaque=false] Opaque value (true/false) 18235 * @param {Array} [options.matrix] Filter matrix 18236 */ 18237 initialize: function(options) { 18238 options = options || { }; 18239 18240 this.opaque = options.opaque; 18241 this.matrix = options.matrix || [ 18242 0, 0, 0, 18243 0, 1, 0, 18244 0, 0, 0 18245 ]; 18246 }, 18247 18248 /** 18249 * Applies filter to canvas element 18250 * @param {Object} canvasEl Canvas element to apply filter to 18251 */ 18252 applyTo: function(canvasEl) { 18253 18254 var weights = this.matrix, 18255 context = canvasEl.getContext('2d'), 18256 pixels = context.getImageData(0, 0, canvasEl.width, canvasEl.height), 18257 18258 side = Math.round(Math.sqrt(weights.length)), 18259 halfSide = Math.floor(side/2), 18260 src = pixels.data, 18261 sw = pixels.width, 18262 sh = pixels.height, 18263 output = context.createImageData(sw, sh), 18264 dst = output.data, 18265 // go through the destination image pixels 18266 alphaFac = this.opaque ? 1 : 0, 18267 r, g, b, a, dstOff, 18268 scx, scy, srcOff, wt; 18269 18270 for (var y = 0; y < sh; y++) { 18271 for (var x = 0; x < sw; x++) { 18272 dstOff = (y * sw + x) * 4; 18273 // calculate the weighed sum of the source image pixels that 18274 // fall under the convolution matrix 18275 r = 0; g = 0; b = 0; a = 0; 18276 18277 for (var cy = 0; cy < side; cy++) { 18278 for (var cx = 0; cx < side; cx++) { 18279 scy = y + cy - halfSide; 18280 scx = x + cx - halfSide; 18281 18282 /* jshint maxdepth:5 */ 18283 if (scy < 0 || scy > sh || scx < 0 || scx > sw) { 18284 continue; 18285 } 18286 18287 srcOff = (scy * sw + scx) * 4; 18288 wt = weights[cy * side + cx]; 18289 18290 r += src[srcOff] * wt; 18291 g += src[srcOff + 1] * wt; 18292 b += src[srcOff + 2] * wt; 18293 a += src[srcOff + 3] * wt; 18294 } 18295 } 18296 dst[dstOff] = r; 18297 dst[dstOff + 1] = g; 18298 dst[dstOff + 2] = b; 18299 dst[dstOff + 3] = a + alphaFac * (255 - a); 18300 } 18301 } 18302 18303 context.putImageData(output, 0, 0); 18304 }, 18305 18306 /** 18307 * Returns object representation of an instance 18308 * @return {Object} Object representation of an instance 18309 */ 18310 toObject: function() { 18311 return extend(this.callSuper('toObject'), { 18312 opaque: this.opaque, 18313 matrix: this.matrix 18314 }); 18315 } 18316 }); 18317 18318 /** 18319 * Returns filter instance from an object representation 18320 * @static 18321 * @param {Object} object Object to create an instance from 18322 * @return {fabric.Image.filters.Convolute} Instance of fabric.Image.filters.Convolute 18323 */ 18324 fabric.Image.filters.Convolute.fromObject = function(object) { 18325 return new fabric.Image.filters.Convolute(object); 18326 }; 18327 18328 })(typeof exports !== 'undefined' ? exports : this); 18329 18330 18331 (function(global) { 18332 18333 'use strict'; 18334 18335 var fabric = global.fabric || (global.fabric = { }), 18336 extend = fabric.util.object.extend; 18337 18338 /** 18339 * GradientTransparency filter class 18340 * @class fabric.Image.filters.GradientTransparency 18341 * @memberOf fabric.Image.filters 18342 * @extends fabric.Image.filters.BaseFilter 18343 * @see {@link fabric.Image.filters.GradientTransparency#initialize} for constructor definition 18344 * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} 18345 * @example 18346 * var filter = new fabric.Image.filters.GradientTransparency({ 18347 * threshold: 200 18348 * }); 18349 * object.filters.push(filter); 18350 * object.applyFilters(canvas.renderAll.bind(canvas)); 18351 */ 18352 fabric.Image.filters.GradientTransparency = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.GradientTransparency.prototype */ { 18353 18354 /** 18355 * Filter type 18356 * @param {String} type 18357 * @default 18358 */ 18359 type: 'GradientTransparency', 18360 18361 /** 18362 * Constructor 18363 * @memberOf fabric.Image.filters.GradientTransparency.prototype 18364 * @param {Object} [options] Options object 18365 * @param {Number} [options.threshold=100] Threshold value 18366 */ 18367 initialize: function(options) { 18368 options = options || { }; 18369 this.threshold = options.threshold || 100; 18370 }, 18371 18372 /** 18373 * Applies filter to canvas element 18374 * @param {Object} canvasEl Canvas element to apply filter to 18375 */ 18376 applyTo: function(canvasEl) { 18377 var context = canvasEl.getContext('2d'), 18378 imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), 18379 data = imageData.data, 18380 threshold = this.threshold, 18381 total = data.length; 18382 18383 for (var i = 0, len = data.length; i < len; i += 4) { 18384 data[i + 3] = threshold + 255 * (total - i) / total; 18385 } 18386 18387 context.putImageData(imageData, 0, 0); 18388 }, 18389 18390 /** 18391 * Returns object representation of an instance 18392 * @return {Object} Object representation of an instance 18393 */ 18394 toObject: function() { 18395 return extend(this.callSuper('toObject'), { 18396 threshold: this.threshold 18397 }); 18398 } 18399 }); 18400 18401 /** 18402 * Returns filter instance from an object representation 18403 * @static 18404 * @param {Object} object Object to create an instance from 18405 * @return {fabric.Image.filters.GradientTransparency} Instance of fabric.Image.filters.GradientTransparency 18406 */ 18407 fabric.Image.filters.GradientTransparency.fromObject = function(object) { 18408 return new fabric.Image.filters.GradientTransparency(object); 18409 }; 18410 18411 })(typeof exports !== 'undefined' ? exports : this); 18412 18413 18414 (function(global) { 18415 18416 'use strict'; 18417 18418 var fabric = global.fabric || (global.fabric = { }); 18419 18420 /** 18421 * Grayscale image filter class 18422 * @class fabric.Image.filters.Grayscale 18423 * @memberOf fabric.Image.filters 18424 * @extends fabric.Image.filters.BaseFilter 18425 * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} 18426 * @example 18427 * var filter = new fabric.Image.filters.Grayscale(); 18428 * object.filters.push(filter); 18429 * object.applyFilters(canvas.renderAll.bind(canvas)); 18430 */ 18431 fabric.Image.filters.Grayscale = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Grayscale.prototype */ { 18432 18433 /** 18434 * Filter type 18435 * @param {String} type 18436 * @default 18437 */ 18438 type: 'Grayscale', 18439 18440 /** 18441 * Applies filter to canvas element 18442 * @memberOf fabric.Image.filters.Grayscale.prototype 18443 * @param {Object} canvasEl Canvas element to apply filter to 18444 */ 18445 applyTo: function(canvasEl) { 18446 var context = canvasEl.getContext('2d'), 18447 imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), 18448 data = imageData.data, 18449 len = imageData.width * imageData.height * 4, 18450 index = 0, 18451 average; 18452 18453 while (index < len) { 18454 average = (data[index] + data[index + 1] + data[index + 2]) / 3; 18455 data[index] = average; 18456 data[index + 1] = average; 18457 data[index + 2] = average; 18458 index += 4; 18459 } 18460 18461 context.putImageData(imageData, 0, 0); 18462 } 18463 }); 18464 18465 /** 18466 * Returns filter instance from an object representation 18467 * @static 18468 * @return {fabric.Image.filters.Grayscale} Instance of fabric.Image.filters.Grayscale 18469 */ 18470 fabric.Image.filters.Grayscale.fromObject = function() { 18471 return new fabric.Image.filters.Grayscale(); 18472 }; 18473 18474 })(typeof exports !== 'undefined' ? exports : this); 18475 18476 18477 (function(global) { 18478 18479 'use strict'; 18480 18481 var fabric = global.fabric || (global.fabric = { }); 18482 18483 /** 18484 * Invert filter class 18485 * @class fabric.Image.filters.Invert 18486 * @memberOf fabric.Image.filters 18487 * @extends fabric.Image.filters.BaseFilter 18488 * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} 18489 * @example 18490 * var filter = new fabric.Image.filters.Invert(); 18491 * object.filters.push(filter); 18492 * object.applyFilters(canvas.renderAll.bind(canvas)); 18493 */ 18494 fabric.Image.filters.Invert = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Invert.prototype */ { 18495 18496 /** 18497 * Filter type 18498 * @param {String} type 18499 * @default 18500 */ 18501 type: 'Invert', 18502 18503 /** 18504 * Applies filter to canvas element 18505 * @memberOf fabric.Image.filters.Invert.prototype 18506 * @param {Object} canvasEl Canvas element to apply filter to 18507 */ 18508 applyTo: function(canvasEl) { 18509 var context = canvasEl.getContext('2d'), 18510 imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), 18511 data = imageData.data, 18512 iLen = data.length, i; 18513 18514 for (i = 0; i < iLen; i+=4) { 18515 data[i] = 255 - data[i]; 18516 data[i + 1] = 255 - data[i + 1]; 18517 data[i + 2] = 255 - data[i + 2]; 18518 } 18519 18520 context.putImageData(imageData, 0, 0); 18521 } 18522 }); 18523 18524 /** 18525 * Returns filter instance from an object representation 18526 * @static 18527 * @return {fabric.Image.filters.Invert} Instance of fabric.Image.filters.Invert 18528 */ 18529 fabric.Image.filters.Invert.fromObject = function() { 18530 return new fabric.Image.filters.Invert(); 18531 }; 18532 18533 })(typeof exports !== 'undefined' ? exports : this); 18534 18535 18536 (function(global) { 18537 18538 'use strict'; 18539 18540 var fabric = global.fabric || (global.fabric = { }), 18541 extend = fabric.util.object.extend; 18542 18543 /** 18544 * Mask filter class 18545 * See http://resources.aleph-1.com/mask/ 18546 * @class fabric.Image.filters.Mask 18547 * @memberOf fabric.Image.filters 18548 * @extends fabric.Image.filters.BaseFilter 18549 * @see {@link fabric.Image.filters.Mask#initialize} for constructor definition 18550 */ 18551 fabric.Image.filters.Mask = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Mask.prototype */ { 18552 18553 /** 18554 * Filter type 18555 * @param {String} type 18556 * @default 18557 */ 18558 type: 'Mask', 18559 18560 /** 18561 * Constructor 18562 * @memberOf fabric.Image.filters.Mask.prototype 18563 * @param {Object} [options] Options object 18564 * @param {fabric.Image} [options.mask] Mask image object 18565 * @param {Number} [options.channel=0] Rgb channel (0, 1, 2 or 3) 18566 */ 18567 initialize: function(options) { 18568 options = options || { }; 18569 18570 this.mask = options.mask; 18571 this.channel = [ 0, 1, 2, 3 ].indexOf(options.channel) > -1 ? options.channel : 0; 18572 }, 18573 18574 /** 18575 * Applies filter to canvas element 18576 * @param {Object} canvasEl Canvas element to apply filter to 18577 */ 18578 applyTo: function(canvasEl) { 18579 if (!this.mask) { 18580 return; 18581 } 18582 18583 var context = canvasEl.getContext('2d'), 18584 imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), 18585 data = imageData.data, 18586 maskEl = this.mask.getElement(), 18587 maskCanvasEl = fabric.util.createCanvasElement(), 18588 channel = this.channel, 18589 i, 18590 iLen = imageData.width * imageData.height * 4; 18591 18592 maskCanvasEl.width = canvasEl.width; 18593 maskCanvasEl.height = canvasEl.height; 18594 18595 maskCanvasEl.getContext('2d').drawImage(maskEl, 0, 0, canvasEl.width, canvasEl.height); 18596 18597 var maskImageData = maskCanvasEl.getContext('2d').getImageData(0, 0, canvasEl.width, canvasEl.height), 18598 maskData = maskImageData.data; 18599 18600 for (i = 0; i < iLen; i += 4) { 18601 data[i + 3] = maskData[i + channel]; 18602 } 18603 18604 context.putImageData(imageData, 0, 0); 18605 }, 18606 18607 /** 18608 * Returns object representation of an instance 18609 * @return {Object} Object representation of an instance 18610 */ 18611 toObject: function() { 18612 return extend(this.callSuper('toObject'), { 18613 mask: this.mask.toObject(), 18614 channel: this.channel 18615 }); 18616 } 18617 }); 18618 18619 /** 18620 * Returns filter instance from an object representation 18621 * @static 18622 * @param {Object} object Object to create an instance from 18623 * @param {Function} [callback] Callback to invoke when a mask filter instance is created 18624 */ 18625 fabric.Image.filters.Mask.fromObject = function(object, callback) { 18626 fabric.util.loadImage(object.mask.src, function(img) { 18627 object.mask = new fabric.Image(img, object.mask); 18628 callback && callback(new fabric.Image.filters.Mask(object)); 18629 }); 18630 }; 18631 18632 /** 18633 * Indicates that instances of this type are async 18634 * @static 18635 * @type Boolean 18636 * @default 18637 */ 18638 fabric.Image.filters.Mask.async = true; 18639 18640 })(typeof exports !== 'undefined' ? exports : this); 18641 18642 18643 (function(global) { 18644 18645 'use strict'; 18646 18647 var fabric = global.fabric || (global.fabric = { }), 18648 extend = fabric.util.object.extend; 18649 18650 /** 18651 * Noise filter class 18652 * @class fabric.Image.filters.Noise 18653 * @memberOf fabric.Image.filters 18654 * @extends fabric.Image.filters.BaseFilter 18655 * @see {@link fabric.Image.filters.Noise#initialize} for constructor definition 18656 * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} 18657 * @example 18658 * var filter = new fabric.Image.filters.Noise({ 18659 * noise: 700 18660 * }); 18661 * object.filters.push(filter); 18662 * object.applyFilters(canvas.renderAll.bind(canvas)); 18663 */ 18664 fabric.Image.filters.Noise = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Noise.prototype */ { 18665 18666 /** 18667 * Filter type 18668 * @param {String} type 18669 * @default 18670 */ 18671 type: 'Noise', 18672 18673 /** 18674 * Constructor 18675 * @memberOf fabric.Image.filters.Noise.prototype 18676 * @param {Object} [options] Options object 18677 * @param {Number} [options.noise=0] Noise value 18678 */ 18679 initialize: function(options) { 18680 options = options || { }; 18681 this.noise = options.noise || 0; 18682 }, 18683 18684 /** 18685 * Applies filter to canvas element 18686 * @param {Object} canvasEl Canvas element to apply filter to 18687 */ 18688 applyTo: function(canvasEl) { 18689 var context = canvasEl.getContext('2d'), 18690 imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), 18691 data = imageData.data, 18692 noise = this.noise, rand; 18693 18694 for (var i = 0, len = data.length; i < len; i += 4) { 18695 18696 rand = (0.5 - Math.random()) * noise; 18697 18698 data[i] += rand; 18699 data[i + 1] += rand; 18700 data[i + 2] += rand; 18701 } 18702 18703 context.putImageData(imageData, 0, 0); 18704 }, 18705 18706 /** 18707 * Returns object representation of an instance 18708 * @return {Object} Object representation of an instance 18709 */ 18710 toObject: function() { 18711 return extend(this.callSuper('toObject'), { 18712 noise: this.noise 18713 }); 18714 } 18715 }); 18716 18717 /** 18718 * Returns filter instance from an object representation 18719 * @static 18720 * @param {Object} object Object to create an instance from 18721 * @return {fabric.Image.filters.Noise} Instance of fabric.Image.filters.Noise 18722 */ 18723 fabric.Image.filters.Noise.fromObject = function(object) { 18724 return new fabric.Image.filters.Noise(object); 18725 }; 18726 18727 })(typeof exports !== 'undefined' ? exports : this); 18728 18729 18730 (function(global) { 18731 18732 'use strict'; 18733 18734 var fabric = global.fabric || (global.fabric = { }), 18735 extend = fabric.util.object.extend; 18736 18737 /** 18738 * Pixelate filter class 18739 * @class fabric.Image.filters.Pixelate 18740 * @memberOf fabric.Image.filters 18741 * @extends fabric.Image.filters.BaseFilter 18742 * @see {@link fabric.Image.filters.Pixelate#initialize} for constructor definition 18743 * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} 18744 * @example 18745 * var filter = new fabric.Image.filters.Pixelate({ 18746 * blocksize: 8 18747 * }); 18748 * object.filters.push(filter); 18749 * object.applyFilters(canvas.renderAll.bind(canvas)); 18750 */ 18751 fabric.Image.filters.Pixelate = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Pixelate.prototype */ { 18752 18753 /** 18754 * Filter type 18755 * @param {String} type 18756 * @default 18757 */ 18758 type: 'Pixelate', 18759 18760 /** 18761 * Constructor 18762 * @memberOf fabric.Image.filters.Pixelate.prototype 18763 * @param {Object} [options] Options object 18764 * @param {Number} [options.blocksize=4] Blocksize for pixelate 18765 */ 18766 initialize: function(options) { 18767 options = options || { }; 18768 this.blocksize = options.blocksize || 4; 18769 }, 18770 18771 /** 18772 * Applies filter to canvas element 18773 * @param {Object} canvasEl Canvas element to apply filter to 18774 */ 18775 applyTo: function(canvasEl) { 18776 var context = canvasEl.getContext('2d'), 18777 imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), 18778 data = imageData.data, 18779 iLen = imageData.height, 18780 jLen = imageData.width, 18781 index, i, j, r, g, b, a; 18782 18783 for (i = 0; i < iLen; i += this.blocksize) { 18784 for (j = 0; j < jLen; j += this.blocksize) { 18785 18786 index = (i * 4) * jLen + (j * 4); 18787 18788 r = data[index]; 18789 g = data[index + 1]; 18790 b = data[index + 2]; 18791 a = data[index + 3]; 18792 18793 /* 18794 blocksize: 4 18795 18796 [1,x,x,x,1] 18797 [x,x,x,x,1] 18798 [x,x,x,x,1] 18799 [x,x,x,x,1] 18800 [1,1,1,1,1] 18801 */ 18802 18803 for (var _i = i, _ilen = i + this.blocksize; _i < _ilen; _i++) { 18804 for (var _j = j, _jlen = j + this.blocksize; _j < _jlen; _j++) { 18805 index = (_i * 4) * jLen + (_j * 4); 18806 data[index] = r; 18807 data[index + 1] = g; 18808 data[index + 2] = b; 18809 data[index + 3] = a; 18810 } 18811 } 18812 } 18813 } 18814 18815 context.putImageData(imageData, 0, 0); 18816 }, 18817 18818 /** 18819 * Returns object representation of an instance 18820 * @return {Object} Object representation of an instance 18821 */ 18822 toObject: function() { 18823 return extend(this.callSuper('toObject'), { 18824 blocksize: this.blocksize 18825 }); 18826 } 18827 }); 18828 18829 /** 18830 * Returns filter instance from an object representation 18831 * @static 18832 * @param {Object} object Object to create an instance from 18833 * @return {fabric.Image.filters.Pixelate} Instance of fabric.Image.filters.Pixelate 18834 */ 18835 fabric.Image.filters.Pixelate.fromObject = function(object) { 18836 return new fabric.Image.filters.Pixelate(object); 18837 }; 18838 18839 })(typeof exports !== 'undefined' ? exports : this); 18840 18841 18842 (function(global) { 18843 18844 'use strict'; 18845 18846 var fabric = global.fabric || (global.fabric = { }), 18847 extend = fabric.util.object.extend; 18848 18849 /** 18850 * Remove white filter class 18851 * @class fabric.Image.filters.RemoveWhite 18852 * @memberOf fabric.Image.filters 18853 * @extends fabric.Image.filters.BaseFilter 18854 * @see {@link fabric.Image.filters.RemoveWhite#initialize} for constructor definition 18855 * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} 18856 * @example 18857 * var filter = new fabric.Image.filters.RemoveWhite({ 18858 * threshold: 40, 18859 * distance: 140 18860 * }); 18861 * object.filters.push(filter); 18862 * object.applyFilters(canvas.renderAll.bind(canvas)); 18863 */ 18864 fabric.Image.filters.RemoveWhite = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.RemoveWhite.prototype */ { 18865 18866 /** 18867 * Filter type 18868 * @param {String} type 18869 * @default 18870 */ 18871 type: 'RemoveWhite', 18872 18873 /** 18874 * Constructor 18875 * @memberOf fabric.Image.filters.RemoveWhite.prototype 18876 * @param {Object} [options] Options object 18877 * @param {Number} [options.threshold=30] Threshold value 18878 * @param {Number} [options.distance=20] Distance value 18879 */ 18880 initialize: function(options) { 18881 options = options || { }; 18882 this.threshold = options.threshold || 30; 18883 this.distance = options.distance || 20; 18884 }, 18885 18886 /** 18887 * Applies filter to canvas element 18888 * @param {Object} canvasEl Canvas element to apply filter to 18889 */ 18890 applyTo: function(canvasEl) { 18891 var context = canvasEl.getContext('2d'), 18892 imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), 18893 data = imageData.data, 18894 threshold = this.threshold, 18895 distance = this.distance, 18896 limit = 255 - threshold, 18897 abs = Math.abs, 18898 r, g, b; 18899 18900 for (var i = 0, len = data.length; i < len; i += 4) { 18901 r = data[i]; 18902 g = data[i + 1]; 18903 b = data[i + 2]; 18904 18905 if (r > limit && 18906 g > limit && 18907 b > limit && 18908 abs(r - g) < distance && 18909 abs(r - b) < distance && 18910 abs(g - b) < distance 18911 ) { 18912 data[i + 3] = 0; 18913 } 18914 } 18915 18916 context.putImageData(imageData, 0, 0); 18917 }, 18918 18919 /** 18920 * Returns object representation of an instance 18921 * @return {Object} Object representation of an instance 18922 */ 18923 toObject: function() { 18924 return extend(this.callSuper('toObject'), { 18925 threshold: this.threshold, 18926 distance: this.distance 18927 }); 18928 } 18929 }); 18930 18931 /** 18932 * Returns filter instance from an object representation 18933 * @static 18934 * @param {Object} object Object to create an instance from 18935 * @return {fabric.Image.filters.RemoveWhite} Instance of fabric.Image.filters.RemoveWhite 18936 */ 18937 fabric.Image.filters.RemoveWhite.fromObject = function(object) { 18938 return new fabric.Image.filters.RemoveWhite(object); 18939 }; 18940 18941 })(typeof exports !== 'undefined' ? exports : this); 18942 18943 18944 (function(global) { 18945 18946 'use strict'; 18947 18948 var fabric = global.fabric || (global.fabric = { }); 18949 18950 /** 18951 * Sepia filter class 18952 * @class fabric.Image.filters.Sepia 18953 * @memberOf fabric.Image.filters 18954 * @extends fabric.Image.filters.BaseFilter 18955 * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} 18956 * @example 18957 * var filter = new fabric.Image.filters.Sepia(); 18958 * object.filters.push(filter); 18959 * object.applyFilters(canvas.renderAll.bind(canvas)); 18960 */ 18961 fabric.Image.filters.Sepia = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Sepia.prototype */ { 18962 18963 /** 18964 * Filter type 18965 * @param {String} type 18966 * @default 18967 */ 18968 type: 'Sepia', 18969 18970 /** 18971 * Applies filter to canvas element 18972 * @memberOf fabric.Image.filters.Sepia.prototype 18973 * @param {Object} canvasEl Canvas element to apply filter to 18974 */ 18975 applyTo: function(canvasEl) { 18976 var context = canvasEl.getContext('2d'), 18977 imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), 18978 data = imageData.data, 18979 iLen = data.length, i, avg; 18980 18981 for (i = 0; i < iLen; i+=4) { 18982 avg = 0.3 * data[i] + 0.59 * data[i + 1] + 0.11 * data[i + 2]; 18983 data[i] = avg + 100; 18984 data[i + 1] = avg + 50; 18985 data[i + 2] = avg + 255; 18986 } 18987 18988 context.putImageData(imageData, 0, 0); 18989 } 18990 }); 18991 18992 /** 18993 * Returns filter instance from an object representation 18994 * @static 18995 * @return {fabric.Image.filters.Sepia} Instance of fabric.Image.filters.Sepia 18996 */ 18997 fabric.Image.filters.Sepia.fromObject = function() { 18998 return new fabric.Image.filters.Sepia(); 18999 }; 19000 19001 })(typeof exports !== 'undefined' ? exports : this); 19002 19003 19004 (function(global) { 19005 19006 'use strict'; 19007 19008 var fabric = global.fabric || (global.fabric = { }); 19009 19010 /** 19011 * Sepia2 filter class 19012 * @class fabric.Image.filters.Sepia2 19013 * @memberOf fabric.Image.filters 19014 * @extends fabric.Image.filters.BaseFilter 19015 * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} 19016 * @example 19017 * var filter = new fabric.Image.filters.Sepia2(); 19018 * object.filters.push(filter); 19019 * object.applyFilters(canvas.renderAll.bind(canvas)); 19020 */ 19021 fabric.Image.filters.Sepia2 = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Sepia2.prototype */ { 19022 19023 /** 19024 * Filter type 19025 * @param {String} type 19026 * @default 19027 */ 19028 type: 'Sepia2', 19029 19030 /** 19031 * Applies filter to canvas element 19032 * @memberOf fabric.Image.filters.Sepia.prototype 19033 * @param {Object} canvasEl Canvas element to apply filter to 19034 */ 19035 applyTo: function(canvasEl) { 19036 var context = canvasEl.getContext('2d'), 19037 imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), 19038 data = imageData.data, 19039 iLen = data.length, i, r, g, b; 19040 19041 for (i = 0; i < iLen; i+=4) { 19042 r = data[i]; 19043 g = data[i + 1]; 19044 b = data[i + 2]; 19045 19046 data[i] = (r * 0.393 + g * 0.769 + b * 0.189 ) / 1.351; 19047 data[i + 1] = (r * 0.349 + g * 0.686 + b * 0.168 ) / 1.203; 19048 data[i + 2] = (r * 0.272 + g * 0.534 + b * 0.131 ) / 2.140; 19049 } 19050 19051 context.putImageData(imageData, 0, 0); 19052 } 19053 }); 19054 19055 /** 19056 * Returns filter instance from an object representation 19057 * @static 19058 * @return {fabric.Image.filters.Sepia2} Instance of fabric.Image.filters.Sepia2 19059 */ 19060 fabric.Image.filters.Sepia2.fromObject = function() { 19061 return new fabric.Image.filters.Sepia2(); 19062 }; 19063 19064 })(typeof exports !== 'undefined' ? exports : this); 19065 19066 19067 (function(global) { 19068 19069 'use strict'; 19070 19071 var fabric = global.fabric || (global.fabric = { }), 19072 extend = fabric.util.object.extend; 19073 19074 /** 19075 * Tint filter class 19076 * Adapted from <a href="https://github.com/mezzoblue/PaintbrushJS">https://github.com/mezzoblue/PaintbrushJS</a> 19077 * @class fabric.Image.filters.Tint 19078 * @memberOf fabric.Image.filters 19079 * @extends fabric.Image.filters.BaseFilter 19080 * @see {@link fabric.Image.filters.Tint#initialize} for constructor definition 19081 * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} 19082 * @example <caption>Tint filter with hex color and opacity</caption> 19083 * var filter = new fabric.Image.filters.Tint({ 19084 * color: '#3513B0', 19085 * opacity: 0.5 19086 * }); 19087 * object.filters.push(filter); 19088 * object.applyFilters(canvas.renderAll.bind(canvas)); 19089 * @example <caption>Tint filter with rgba color</caption> 19090 * var filter = new fabric.Image.filters.Tint({ 19091 * color: 'rgba(53, 21, 176, 0.5)' 19092 * }); 19093 * object.filters.push(filter); 19094 * object.applyFilters(canvas.renderAll.bind(canvas)); 19095 */ 19096 fabric.Image.filters.Tint = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Tint.prototype */ { 19097 19098 /** 19099 * Filter type 19100 * @param {String} type 19101 * @default 19102 */ 19103 type: 'Tint', 19104 19105 /** 19106 * Constructor 19107 * @memberOf fabric.Image.filters.Tint.prototype 19108 * @param {Object} [options] Options object 19109 * @param {String} [options.color=#000000] Color to tint the image with 19110 * @param {Number} [options.opacity] Opacity value that controls the tint effect's transparency (0..1) 19111 */ 19112 initialize: function(options) { 19113 options = options || { }; 19114 19115 this.color = options.color || '#000000'; 19116 this.opacity = typeof options.opacity !== 'undefined' 19117 ? options.opacity 19118 : new fabric.Color(this.color).getAlpha(); 19119 }, 19120 19121 /** 19122 * Applies filter to canvas element 19123 * @param {Object} canvasEl Canvas element to apply filter to 19124 */ 19125 applyTo: function(canvasEl) { 19126 var context = canvasEl.getContext('2d'), 19127 imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), 19128 data = imageData.data, 19129 iLen = data.length, i, 19130 tintR, tintG, tintB, 19131 r, g, b, alpha1, 19132 source; 19133 19134 source = new fabric.Color(this.color).getSource(); 19135 19136 tintR = source[0] * this.opacity; 19137 tintG = source[1] * this.opacity; 19138 tintB = source[2] * this.opacity; 19139 19140 alpha1 = 1 - this.opacity; 19141 19142 for (i = 0; i < iLen; i+=4) { 19143 r = data[i]; 19144 g = data[i + 1]; 19145 b = data[i + 2]; 19146 19147 // alpha compositing 19148 data[i] = tintR + r * alpha1; 19149 data[i + 1] = tintG + g * alpha1; 19150 data[i + 2] = tintB + b * alpha1; 19151 } 19152 19153 context.putImageData(imageData, 0, 0); 19154 }, 19155 19156 /** 19157 * Returns object representation of an instance 19158 * @return {Object} Object representation of an instance 19159 */ 19160 toObject: function() { 19161 return extend(this.callSuper('toObject'), { 19162 color: this.color, 19163 opacity: this.opacity 19164 }); 19165 } 19166 }); 19167 19168 /** 19169 * Returns filter instance from an object representation 19170 * @static 19171 * @param {Object} object Object to create an instance from 19172 * @return {fabric.Image.filters.Tint} Instance of fabric.Image.filters.Tint 19173 */ 19174 fabric.Image.filters.Tint.fromObject = function(object) { 19175 return new fabric.Image.filters.Tint(object); 19176 }; 19177 19178 })(typeof exports !== 'undefined' ? exports : this); 19179 19180 19181 (function(global) { 19182 19183 'use strict'; 19184 19185 var fabric = global.fabric || (global.fabric = { }), 19186 extend = fabric.util.object.extend; 19187 19188 /** 19189 * Multiply filter class 19190 * Adapted from <a href="http://www.laurenscorijn.com/articles/colormath-basics">http://www.laurenscorijn.com/articles/colormath-basics</a> 19191 * @class fabric.Image.filters.Multiply 19192 * @memberOf fabric.Image.filters 19193 * @extends fabric.Image.filters.BaseFilter 19194 * @example <caption>Multiply filter with hex color</caption> 19195 * var filter = new fabric.Image.filters.Multiply({ 19196 * color: '#F0F' 19197 * }); 19198 * object.filters.push(filter); 19199 * object.applyFilters(canvas.renderAll.bind(canvas)); 19200 * @example <caption>Multiply filter with rgb color</caption> 19201 * var filter = new fabric.Image.filters.Multiply({ 19202 * color: 'rgb(53, 21, 176)' 19203 * }); 19204 * object.filters.push(filter); 19205 * object.applyFilters(canvas.renderAll.bind(canvas)); 19206 */ 19207 fabric.Image.filters.Multiply = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Multiply.prototype */ { 19208 19209 /** 19210 * Filter type 19211 * @param {String} type 19212 * @default 19213 */ 19214 type: 'Multiply', 19215 19216 /** 19217 * Constructor 19218 * @memberOf fabric.Image.filters.Multiply.prototype 19219 * @param {Object} [options] Options object 19220 * @param {String} [options.color=#000000] Color to multiply the image pixels with 19221 */ 19222 initialize: function(options) { 19223 options = options || { }; 19224 19225 this.color = options.color || '#000000'; 19226 }, 19227 19228 /** 19229 * Applies filter to canvas element 19230 * @param {Object} canvasEl Canvas element to apply filter to 19231 */ 19232 applyTo: function(canvasEl) { 19233 var context = canvasEl.getContext('2d'), 19234 imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), 19235 data = imageData.data, 19236 iLen = data.length, i, 19237 source; 19238 19239 source = new fabric.Color(this.color).getSource(); 19240 19241 for (i = 0; i < iLen; i+=4) { 19242 data[i] *= source[0] / 255; 19243 data[i + 1] *= source[1] / 255; 19244 data[i + 2] *= source[2] / 255; 19245 } 19246 19247 context.putImageData(imageData, 0, 0); 19248 }, 19249 19250 /** 19251 * Returns object representation of an instance 19252 * @return {Object} Object representation of an instance 19253 */ 19254 toObject: function() { 19255 return extend(this.callSuper('toObject'), { 19256 color: this.color 19257 }); 19258 } 19259 }); 19260 19261 /** 19262 * Returns filter instance from an object representation 19263 * @static 19264 * @param {Object} object Object to create an instance from 19265 * @return {fabric.Image.filters.Multiply} Instance of fabric.Image.filters.Multiply 19266 */ 19267 fabric.Image.filters.Multiply.fromObject = function(object) { 19268 return new fabric.Image.filters.Multiply(object); 19269 }; 19270 19271 })(typeof exports !== 'undefined' ? exports : this); 19272 19273 19274 (function(global) { 19275 'use strict'; 19276 19277 var fabric = global.fabric; 19278 19279 /** 19280 * Color Blend filter class 19281 * @class fabric.Image.filter.Blend 19282 * @memberOf fabric.Image.filters 19283 * @extends fabric.Image.filters.BaseFilter 19284 * @example 19285 * var filter = new fabric.Image.filters.Blend({ 19286 * color: '#000', 19287 * mode: 'multiply' 19288 * }); 19289 * 19290 * var filter = new fabric.Image.filters.Blend({ 19291 * image: fabricImageObject, 19292 * mode: 'multiply', 19293 * alpha: 0.5 19294 * }); 19295 19296 * object.filters.push(filter); 19297 * object.applyFilters(canvas.renderAll.bind(canvas)); 19298 */ 19299 fabric.Image.filters.Blend = fabric.util.createClass({ 19300 type: 'Blend', 19301 19302 initialize: function(options) { 19303 options = options || {}; 19304 this.color = options.color || '#000'; 19305 this.image = options.image || false; 19306 this.mode = options.mode || 'multiply'; 19307 this.alpha = options.alpha || 1; 19308 }, 19309 19310 applyTo: function(canvasEl) { 19311 var context = canvasEl.getContext('2d'), 19312 imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), 19313 data = imageData.data, 19314 tr, tg, tb, 19315 r, g, b, 19316 _r, _g, _b, 19317 source, 19318 isImage = false; 19319 19320 if (this.image) { 19321 // Blend images 19322 isImage = true; 19323 19324 var _el = fabric.util.createCanvasElement(); 19325 _el.width = this.image.width; 19326 _el.height = this.image.height; 19327 19328 var tmpCanvas = new fabric.StaticCanvas(_el); 19329 tmpCanvas.add(this.image); 19330 var context2 = tmpCanvas.getContext('2d'); 19331 source = context2.getImageData(0, 0, tmpCanvas.width, tmpCanvas.height).data; 19332 } 19333 else { 19334 // Blend color 19335 source = new fabric.Color(this.color).getSource(); 19336 19337 tr = source[0] * this.alpha; 19338 tg = source[1] * this.alpha; 19339 tb = source[2] * this.alpha; 19340 } 19341 19342 for (var i = 0, len = data.length; i < len; i += 4) { 19343 19344 r = data[i]; 19345 g = data[i + 1]; 19346 b = data[i + 2]; 19347 19348 if (isImage) { 19349 tr = source[i] * this.alpha; 19350 tg = source[i + 1] * this.alpha; 19351 tb = source[i + 2] * this.alpha; 19352 } 19353 19354 switch (this.mode) { 19355 case 'multiply': 19356 data[i] = r * tr / 255; 19357 data[i + 1] = g * tg / 255; 19358 data[i + 2] = b * tb / 255; 19359 break; 19360 case 'screen': 19361 data[i] = 1 - (1 - r) * (1 - tr); 19362 data[i + 1] = 1 - (1 - g) * (1 - tg); 19363 data[i + 2] = 1 - (1 - b) * (1 - tb); 19364 break; 19365 case 'add': 19366 data[i] = Math.min(255, r + tr); 19367 data[i + 1] = Math.min(255, g + tg); 19368 data[i + 2] = Math.min(255, b + tb); 19369 break; 19370 case 'diff': 19371 case 'difference': 19372 data[i] = Math.abs(r - tr); 19373 data[i + 1] = Math.abs(g - tg); 19374 data[i + 2] = Math.abs(b - tb); 19375 break; 19376 case 'subtract': 19377 _r = r - tr; 19378 _g = g - tg; 19379 _b = b - tb; 19380 19381 data[i] = (_r < 0) ? 0 : _r; 19382 data[i + 1] = (_g < 0) ? 0 : _g; 19383 data[i + 2] = (_b < 0) ? 0 : _b; 19384 break; 19385 case 'darken': 19386 data[i] = Math.min(r, tr); 19387 data[i + 1] = Math.min(g, tg); 19388 data[i + 2] = Math.min(b, tb); 19389 break; 19390 case 'lighten': 19391 data[i] = Math.max(r, tr); 19392 data[i + 1] = Math.max(g, tg); 19393 data[i + 2] = Math.max(b, tb); 19394 break; 19395 } 19396 } 19397 19398 context.putImageData(imageData, 0, 0); 19399 }, 19400 19401 /** 19402 * Returns object representation of an instance 19403 * @return {Object} Object representation of an instance 19404 */ 19405 toObject: function() { 19406 return { 19407 color: this.color, 19408 image: this.image, 19409 mode: this.mode, 19410 alpha: this.alpha 19411 }; 19412 } 19413 }); 19414 19415 fabric.Image.filters.Blend.fromObject = function(object) { 19416 return new fabric.Image.filters.Blend(object); 19417 }; 19418 })(typeof exports !== 'undefined' ? exports : this); 19419 19420 19421 (function(global) { 19422 19423 'use strict'; 19424 19425 var fabric = global.fabric || (global.fabric = { }), pow = Math.pow, floor = Math.floor, 19426 sqrt = Math.sqrt, abs = Math.abs, max = Math.max, round = Math.round, sin = Math.sin, 19427 ceil = Math.ceil; 19428 19429 /** 19430 * Resize image filter class 19431 * @class fabric.Image.filters.Resize 19432 * @memberOf fabric.Image.filters 19433 * @extends fabric.Image.filters.BaseFilter 19434 * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} 19435 * @example 19436 * var filter = new fabric.Image.filters.Resize(); 19437 * object.filters.push(filter); 19438 * object.applyFilters(canvas.renderAll.bind(canvas)); 19439 */ 19440 fabric.Image.filters.Resize = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Resize.prototype */ { 19441 19442 /** 19443 * Filter type 19444 * @param {String} type 19445 * @default 19446 */ 19447 type: 'Resize', 19448 19449 /** 19450 * Resize type 19451 * @param {String} resizeType 19452 * @default 19453 */ 19454 resizeType: 'hermite', 19455 19456 /** 19457 * Scale factor for resizing, x axis 19458 * @param {Number} scaleX 19459 * @default 19460 */ 19461 scaleX: 0, 19462 19463 /** 19464 * Scale factor for resizing, y axis 19465 * @param {Number} scaleY 19466 * @default 19467 */ 19468 scaleY: 0, 19469 19470 /** 19471 * LanczosLobes parameter for lanczos filter 19472 * @param {Number} lanczosLobes 19473 * @default 19474 */ 19475 lanczosLobes: 3, 19476 19477 /** 19478 * Applies filter to canvas element 19479 * @memberOf fabric.Image.filters.Resize.prototype 19480 * @param {Object} canvasEl Canvas element to apply filter to 19481 */ 19482 applyTo: function(canvasEl, scaleX, scaleY) { 19483 19484 this.rcpScaleX = 1 / scaleX; 19485 this.rcpScaleY = 1 / scaleY; 19486 19487 var oW = canvasEl.width, oH = canvasEl.height, 19488 dW = round(oW * scaleX), dH = round(oH * scaleY), 19489 imageData; 19490 19491 if (this.resizeType === 'sliceHack') { 19492 imageData = this.sliceByTwo(canvasEl, oW, oH, dW, dH); 19493 } 19494 if (this.resizeType === 'hermite') { 19495 imageData = this.hermiteFastResize(canvasEl, oW, oH, dW, dH); 19496 } 19497 if (this.resizeType === 'bilinear') { 19498 imageData = this.bilinearFiltering(canvasEl, oW, oH, dW, dH); 19499 } 19500 if (this.resizeType === 'lanczos') { 19501 imageData = this.lanczosResize(canvasEl, oW, oH, dW, dH); 19502 } 19503 canvasEl.width = dW; 19504 canvasEl.height = dH; 19505 canvasEl.getContext('2d').putImageData(imageData, 0, 0); 19506 }, 19507 19508 sliceByTwo: function(canvasEl, width, height, newWidth, newHeight) { 19509 var context = canvasEl.getContext('2d'), imageData, 19510 multW = 0.5, multH = 0.5, signW = 1, signH = 1, 19511 doneW = false, doneH = false, stepW = width, stepH = height, 19512 tmpCanvas = fabric.util.createCanvasElement(), 19513 tmpCtx = tmpCanvas.getContext('2d'); 19514 newWidth = floor(newWidth); 19515 newHeight = floor(newHeight); 19516 tmpCanvas.width = max(newWidth, width); 19517 tmpCanvas.height = max(newHeight, height); 19518 19519 if (newWidth > width) { 19520 multW = 2; 19521 signW = -1; 19522 } 19523 if (newHeight > height) { 19524 multH = 2; 19525 signH = -1; 19526 } 19527 imageData = context.getImageData(0, 0, width, height); 19528 canvasEl.width = max(newWidth, width); 19529 canvasEl.height = max(newHeight, height); 19530 context.putImageData(imageData, 0, 0); 19531 19532 while (!doneW || !doneH) { 19533 width = stepW; 19534 height = stepH; 19535 if (newWidth * signW < floor(stepW * multW * signW)) { 19536 stepW = floor(stepW * multW); 19537 } 19538 else { 19539 stepW = newWidth; 19540 doneW = true; 19541 } 19542 if (newHeight * signH < floor(stepH * multH * signH)) { 19543 stepH = floor(stepH * multH); 19544 } 19545 else { 19546 stepH = newHeight; 19547 doneH = true; 19548 } 19549 imageData = context.getImageData(0, 0, width, height); 19550 tmpCtx.putImageData(imageData, 0, 0); 19551 context.clearRect(0, 0, stepW, stepH); 19552 context.drawImage(tmpCanvas, 0, 0, width, height, 0, 0, stepW, stepH); 19553 } 19554 return context.getImageData(0, 0, newWidth, newHeight); 19555 }, 19556 19557 lanczosResize: function(canvasEl, oW, oH, dW, dH) { 19558 19559 function lanczosCreate(lobes) { 19560 return function(x) { 19561 if (x > lobes) { 19562 return 0; 19563 } 19564 x *= Math.PI; 19565 if (abs(x) < 1e-16) { 19566 return 1; 19567 } 19568 var xx = x / lobes; 19569 return sin(x) * sin(xx) / x / xx; 19570 }; 19571 } 19572 19573 function process(u) { 19574 var v, i, weight, idx, a, red, green, 19575 blue, alpha, fX, fY; 19576 center.x = (u + 0.5) * ratioX; 19577 icenter.x = floor(center.x); 19578 for (v = 0; v < dH; v++) { 19579 center.y = (v + 0.5) * ratioY; 19580 icenter.y = floor(center.y); 19581 a = 0, red = 0, green = 0, blue = 0, alpha = 0; 19582 for (i = icenter.x - range2X; i <= icenter.x + range2X; i++) { 19583 if (i < 0 || i >= oW) { 19584 continue; 19585 } 19586 fX = floor(1000 * abs(i - center.x)); 19587 if (!cacheLanc[fX]) { 19588 cacheLanc[fX] = { }; 19589 } 19590 for (var j = icenter.y - range2Y; j <= icenter.y + range2Y; j++) { 19591 if (j < 0 || j >= oH) { 19592 continue; 19593 } 19594 fY = floor(1000 * abs(j - center.y)); 19595 if (!cacheLanc[fX][fY]) { 19596 cacheLanc[fX][fY] = lanczos(sqrt(pow(fX * rcpRatioX, 2) + pow(fY * rcpRatioY, 2)) / 1000); 19597 } 19598 weight = cacheLanc[fX][fY]; 19599 if (weight > 0) { 19600 idx = (j * oW + i) * 4; 19601 a += weight; 19602 red += weight * srcData[idx]; 19603 green += weight * srcData[idx + 1]; 19604 blue += weight * srcData[idx + 2]; 19605 alpha += weight * srcData[idx + 3]; 19606 } 19607 } 19608 } 19609 idx = (v * dW + u) * 4; 19610 destData[idx] = red / a; 19611 destData[idx + 1] = green / a; 19612 destData[idx + 2] = blue / a; 19613 destData[idx + 3] = alpha / a; 19614 } 19615 19616 if (++u < dW) { 19617 return process(u); 19618 } 19619 else { 19620 return destImg; 19621 } 19622 } 19623 19624 var context = canvasEl.getContext('2d'), 19625 srcImg = context.getImageData(0, 0, oW, oH), 19626 destImg = context.getImageData(0, 0, dW, dH), 19627 srcData = srcImg.data, destData = destImg.data, 19628 lanczos = lanczosCreate(this.lanczosLobes), 19629 ratioX = this.rcpScaleX, ratioY = this.rcpScaleY, 19630 rcpRatioX = 2 / this.rcpScaleX, rcpRatioY = 2 / this.rcpScaleY, 19631 range2X = ceil(ratioX * this.lanczosLobes / 2), 19632 range2Y = ceil(ratioY * this.lanczosLobes / 2), 19633 cacheLanc = { }, center = { }, icenter = { }; 19634 19635 return process(0); 19636 }, 19637 19638 bilinearFiltering: function(canvasEl, w, h, w2, h2) { 19639 var a, b, c, d, x, y, i, j, xDiff, yDiff, chnl, 19640 color, offset = 0, origPix, ratioX = this.rcpScaleX, 19641 ratioY = this.rcpScaleY, context = canvasEl.getContext('2d'), 19642 w4 = 4 * (w - 1), img = context.getImageData(0, 0, w, h), 19643 pixels = img.data, destImage = context.getImageData(0, 0, w2, h2), 19644 destPixels = destImage.data; 19645 for (i = 0; i < h2; i++) { 19646 for (j = 0; j < w2; j++) { 19647 x = floor(ratioX * j); 19648 y = floor(ratioY * i); 19649 xDiff = ratioX * j - x; 19650 yDiff = ratioY * i - y; 19651 origPix = 4 * (y * w + x); 19652 19653 for (chnl = 0; chnl < 4; chnl++) { 19654 a = pixels[origPix + chnl]; 19655 b = pixels[origPix + 4 + chnl]; 19656 c = pixels[origPix + w4 + chnl]; 19657 d = pixels[origPix + w4 + 4 + chnl]; 19658 color = a * (1 - xDiff) * (1 - yDiff) + b * xDiff * (1 - yDiff) + 19659 c * yDiff * (1 - xDiff) + d * xDiff * yDiff; 19660 destPixels[offset++] = color; 19661 } 19662 } 19663 } 19664 return destImage; 19665 }, 19666 19667 hermiteFastResize: function(canvasEl, oW, oH, dW, dH) { 19668 var ratioW = this.rcpScaleX, ratioH = this.rcpScaleY, 19669 ratioWHalf = ceil(ratioW / 2), 19670 ratioHHalf = ceil(ratioH / 2), 19671 context = canvasEl.getContext('2d'), 19672 img = context.getImageData(0, 0, oW, oH), data = img.data, 19673 img2 = context.getImageData(0, 0, dW, dH), data2 = img2.data; 19674 for (var j = 0; j < dH; j++) { 19675 for (var i = 0; i < dW; i++) { 19676 var x2 = (i + j * dW) * 4, weight = 0, weights = 0, weightsAlpha = 0, 19677 gxR = 0, gxG = 0, gxB = 0, gxA = 0, centerY = (j + 0.5) * ratioH; 19678 for (var yy = floor(j * ratioH); yy < (j + 1) * ratioH; yy++) { 19679 var dy = abs(centerY - (yy + 0.5)) / ratioHHalf, 19680 centerX = (i + 0.5) * ratioW, w0 = dy * dy; 19681 for (var xx = floor(i * ratioW); xx < (i + 1) * ratioW; xx++) { 19682 var dx = abs(centerX - (xx + 0.5)) / ratioWHalf, 19683 w = sqrt(w0 + dx * dx); 19684 /*jshint maxdepth:5 */ 19685 if (w > 1 && w < -1) { 19686 continue; 19687 } 19688 //hermite filter 19689 weight = 2 * w * w * w - 3 * w * w + 1; 19690 if (weight > 0) { 19691 dx = 4 * (xx + yy * oW); 19692 //alpha 19693 gxA += weight * data[dx + 3]; 19694 weightsAlpha += weight; 19695 //colors 19696 /*jshint maxdepth:6 */ 19697 if (data[dx + 3] < 255) { 19698 weight = weight * data[dx + 3] / 250; 19699 } 19700 /*jshint maxdepth:5 */ 19701 gxR += weight * data[dx]; 19702 gxG += weight * data[dx + 1]; 19703 gxB += weight * data[dx + 2]; 19704 weights += weight; 19705 } 19706 /*jshint maxdepth:4 */ 19707 } 19708 } 19709 data2[x2] = gxR / weights; 19710 data2[x2 + 1] = gxG / weights; 19711 data2[x2 + 2] = gxB / weights; 19712 data2[x2 + 3] = gxA / weightsAlpha; 19713 } 19714 } 19715 return img2; 19716 }, 19717 19718 /** 19719 * Returns object representation of an instance 19720 * @return {Object} Object representation of an instance 19721 */ 19722 toObject: function() { 19723 return { 19724 type: this.type, 19725 scaleX: this.scaleX, 19726 scaleY: this.scaleY, 19727 resizeType: this.resizeType, 19728 lanczosLobes: this.lanczosLobes 19729 }; 19730 } 19731 }); 19732 19733 /** 19734 * Returns filter instance from an object representation 19735 * @static 19736 * @return {fabric.Image.filters.Resize} Instance of fabric.Image.filters.Resize 19737 */ 19738 fabric.Image.filters.Resize.fromObject = function(object) { 19739 return new fabric.Image.filters.Resize(object); 19740 }; 19741 19742 })(typeof exports !== 'undefined' ? exports : this); 19743 19744 19745 (function(global) { 19746 19747 'use strict'; 19748 19749 var fabric = global.fabric || (global.fabric = { }), 19750 extend = fabric.util.object.extend, 19751 clone = fabric.util.object.clone, 19752 toFixed = fabric.util.toFixed, 19753 supportsLineDash = fabric.StaticCanvas.supports('setLineDash'), 19754 NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; 19755 19756 if (fabric.Text) { 19757 fabric.warn('fabric.Text is already defined'); 19758 return; 19759 } 19760 19761 var stateProperties = fabric.Object.prototype.stateProperties.concat(); 19762 stateProperties.push( 19763 'fontFamily', 19764 'fontWeight', 19765 'fontSize', 19766 'text', 19767 'textDecoration', 19768 'textAlign', 19769 'fontStyle', 19770 'lineHeight', 19771 'textBackgroundColor' 19772 ); 19773 19774 /** 19775 * Text class 19776 * @class fabric.Text 19777 * @extends fabric.Object 19778 * @return {fabric.Text} thisArg 19779 * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#text} 19780 * @see {@link fabric.Text#initialize} for constructor definition 19781 */ 19782 fabric.Text = fabric.util.createClass(fabric.Object, /** @lends fabric.Text.prototype */ { 19783 19784 /** 19785 * Properties which when set cause object to change dimensions 19786 * @type Object 19787 * @private 19788 */ 19789 _dimensionAffectingProps: { 19790 fontSize: true, 19791 fontWeight: true, 19792 fontFamily: true, 19793 fontStyle: true, 19794 lineHeight: true, 19795 stroke: true, 19796 strokeWidth: true, 19797 text: true, 19798 textAlign: true 19799 }, 19800 19801 /** 19802 * @private 19803 */ 19804 _reNewline: /\r?\n/, 19805 19806 /** 19807 * Use this regular expression to filter for whitespace that is not a new line. 19808 * Mostly used when text is 'justify' aligned. 19809 * @private 19810 */ 19811 _reSpacesAndTabs: /[ \t\r]+/g, 19812 19813 /** 19814 * Retrieves object's fontSize 19815 * @method getFontSize 19816 * @memberOf fabric.Text.prototype 19817 * @return {String} Font size (in pixels) 19818 */ 19819 19820 /** 19821 * Sets object's fontSize 19822 * @method setFontSize 19823 * @memberOf fabric.Text.prototype 19824 * @param {Number} fontSize Font size (in pixels) 19825 * @return {fabric.Text} 19826 * @chainable 19827 */ 19828 19829 /** 19830 * Retrieves object's fontWeight 19831 * @method getFontWeight 19832 * @memberOf fabric.Text.prototype 19833 * @return {(String|Number)} Font weight 19834 */ 19835 19836 /** 19837 * Sets object's fontWeight 19838 * @method setFontWeight 19839 * @memberOf fabric.Text.prototype 19840 * @param {(Number|String)} fontWeight Font weight 19841 * @return {fabric.Text} 19842 * @chainable 19843 */ 19844 19845 /** 19846 * Retrieves object's fontFamily 19847 * @method getFontFamily 19848 * @memberOf fabric.Text.prototype 19849 * @return {String} Font family 19850 */ 19851 19852 /** 19853 * Sets object's fontFamily 19854 * @method setFontFamily 19855 * @memberOf fabric.Text.prototype 19856 * @param {String} fontFamily Font family 19857 * @return {fabric.Text} 19858 * @chainable 19859 */ 19860 19861 /** 19862 * Retrieves object's text 19863 * @method getText 19864 * @memberOf fabric.Text.prototype 19865 * @return {String} text 19866 */ 19867 19868 /** 19869 * Sets object's text 19870 * @method setText 19871 * @memberOf fabric.Text.prototype 19872 * @param {String} text Text 19873 * @return {fabric.Text} 19874 * @chainable 19875 */ 19876 19877 /** 19878 * Retrieves object's textDecoration 19879 * @method getTextDecoration 19880 * @memberOf fabric.Text.prototype 19881 * @return {String} Text decoration 19882 */ 19883 19884 /** 19885 * Sets object's textDecoration 19886 * @method setTextDecoration 19887 * @memberOf fabric.Text.prototype 19888 * @param {String} textDecoration Text decoration 19889 * @return {fabric.Text} 19890 * @chainable 19891 */ 19892 19893 /** 19894 * Retrieves object's fontStyle 19895 * @method getFontStyle 19896 * @memberOf fabric.Text.prototype 19897 * @return {String} Font style 19898 */ 19899 19900 /** 19901 * Sets object's fontStyle 19902 * @method setFontStyle 19903 * @memberOf fabric.Text.prototype 19904 * @param {String} fontStyle Font style 19905 * @return {fabric.Text} 19906 * @chainable 19907 */ 19908 19909 /** 19910 * Retrieves object's lineHeight 19911 * @method getLineHeight 19912 * @memberOf fabric.Text.prototype 19913 * @return {Number} Line height 19914 */ 19915 19916 /** 19917 * Sets object's lineHeight 19918 * @method setLineHeight 19919 * @memberOf fabric.Text.prototype 19920 * @param {Number} lineHeight Line height 19921 * @return {fabric.Text} 19922 * @chainable 19923 */ 19924 19925 /** 19926 * Retrieves object's textAlign 19927 * @method getTextAlign 19928 * @memberOf fabric.Text.prototype 19929 * @return {String} Text alignment 19930 */ 19931 19932 /** 19933 * Sets object's textAlign 19934 * @method setTextAlign 19935 * @memberOf fabric.Text.prototype 19936 * @param {String} textAlign Text alignment 19937 * @return {fabric.Text} 19938 * @chainable 19939 */ 19940 19941 /** 19942 * Retrieves object's textBackgroundColor 19943 * @method getTextBackgroundColor 19944 * @memberOf fabric.Text.prototype 19945 * @return {String} Text background color 19946 */ 19947 19948 /** 19949 * Sets object's textBackgroundColor 19950 * @method setTextBackgroundColor 19951 * @memberOf fabric.Text.prototype 19952 * @param {String} textBackgroundColor Text background color 19953 * @return {fabric.Text} 19954 * @chainable 19955 */ 19956 19957 /** 19958 * Type of an object 19959 * @type String 19960 * @default 19961 */ 19962 type: 'text', 19963 19964 /** 19965 * Font size (in pixels) 19966 * @type Number 19967 * @default 19968 */ 19969 fontSize: 40, 19970 19971 /** 19972 * Font weight (e.g. bold, normal, 400, 600, 800) 19973 * @type {(Number|String)} 19974 * @default 19975 */ 19976 fontWeight: 'normal', 19977 19978 /** 19979 * Font family 19980 * @type String 19981 * @default 19982 */ 19983 fontFamily: 'Times New Roman', 19984 19985 /** 19986 * Text decoration Possible values: "", "underline", "overline" or "line-through". 19987 * @type String 19988 * @default 19989 */ 19990 textDecoration: '', 19991 19992 /** 19993 * Text alignment. Possible values: "left", "center", "right" or "justify". 19994 * @type String 19995 * @default 19996 */ 19997 textAlign: 'left', 19998 19999 /** 20000 * Font style . Possible values: "", "normal", "italic" or "oblique". 20001 * @type String 20002 * @default 20003 */ 20004 fontStyle: '', 20005 20006 /** 20007 * Line height 20008 * @type Number 20009 * @default 20010 */ 20011 lineHeight: 1.16, 20012 20013 /** 20014 * Background color of text lines 20015 * @type String 20016 * @default 20017 */ 20018 textBackgroundColor: '', 20019 20020 /** 20021 * List of properties to consider when checking if 20022 * state of an object is changed ({@link fabric.Object#hasStateChanged}) 20023 * as well as for history (undo/redo) purposes 20024 * @type Array 20025 */ 20026 stateProperties: stateProperties, 20027 20028 /** 20029 * When defined, an object is rendered via stroke and this property specifies its color. 20030 * <b>Backwards incompatibility note:</b> This property was named "strokeStyle" until v1.1.6 20031 * @type String 20032 * @default 20033 */ 20034 stroke: null, 20035 20036 /** 20037 * Shadow object representing shadow of this shape. 20038 * <b>Backwards incompatibility note:</b> This property was named "textShadow" (String) until v1.2.11 20039 * @type fabric.Shadow 20040 * @default 20041 */ 20042 shadow: null, 20043 20044 /** 20045 * @private 20046 */ 20047 _fontSizeFraction: 0.25, 20048 20049 /** 20050 * Text Line proportion to font Size (in pixels) 20051 * @type Number 20052 * @default 20053 */ 20054 _fontSizeMult: 1.13, 20055 20056 /** 20057 * Constructor 20058 * @param {String} text Text string 20059 * @param {Object} [options] Options object 20060 * @return {fabric.Text} thisArg 20061 */ 20062 initialize: function(text, options) { 20063 options = options || { }; 20064 this.text = text; 20065 this.__skipDimension = true; 20066 this.setOptions(options); 20067 this.__skipDimension = false; 20068 this._initDimensions(); 20069 }, 20070 20071 /** 20072 * Renders text object on offscreen canvas, so that it would get dimensions 20073 * @private 20074 */ 20075 _initDimensions: function(ctx) { 20076 if (this.__skipDimension) { 20077 return; 20078 } 20079 if (!ctx) { 20080 ctx = fabric.util.createCanvasElement().getContext('2d'); 20081 this._setTextStyles(ctx); 20082 } 20083 this._textLines = this._splitTextIntoLines(); 20084 this._clearCache(); 20085 //if textAlign is 'justify' i have to disable caching 20086 //when calculating width of text and widths of line. 20087 this._cacheLinesWidth = (this.textAlign !== 'justify'); 20088 this.width = this._getTextWidth(ctx); 20089 this._cacheLinesWidth = true; 20090 this.height = this._getTextHeight(ctx); 20091 }, 20092 20093 /** 20094 * Returns string representation of an instance 20095 * @return {String} String representation of text object 20096 */ 20097 toString: function() { 20098 return '#<fabric.Text (' + this.complexity() + 20099 '): { "text": "' + this.text + '", "fontFamily": "' + this.fontFamily + '" }>'; 20100 }, 20101 20102 /** 20103 * @private 20104 * @param {CanvasRenderingContext2D} ctx Context to render on 20105 */ 20106 _render: function(ctx) { 20107 this.clipTo && fabric.util.clipContext(this, ctx); 20108 this._setOpacity(ctx); 20109 this._setShadow(ctx); 20110 this._setupCompositeOperation(ctx); 20111 this._renderTextBackground(ctx); 20112 this._setStrokeStyles(ctx); 20113 this._setFillStyles(ctx); 20114 this._renderText(ctx); 20115 this._renderTextDecoration(ctx); 20116 this.clipTo && ctx.restore(); 20117 }, 20118 20119 /** 20120 * @private 20121 * @param {CanvasRenderingContext2D} ctx Context to render on 20122 */ 20123 _renderText: function(ctx) { 20124 20125 this._translateForTextAlign(ctx); 20126 this._renderTextFill(ctx); 20127 this._renderTextStroke(ctx); 20128 this._translateForTextAlign(ctx, true); 20129 }, 20130 20131 /** 20132 * @private 20133 * @param {CanvasRenderingContext2D} ctx Context to render on 20134 * @param {Boolean} back Indicates if translate back or forward 20135 */ 20136 _translateForTextAlign: function(ctx, back) { 20137 if (this.textAlign !== 'left' && this.textAlign !== 'justify') { 20138 var sign = back ? -1 : 1; 20139 ctx.translate(this.textAlign === 'center' ? (sign * this.width / 2) : sign * this.width, 0); 20140 } 20141 }, 20142 20143 /** 20144 * @private 20145 * @param {CanvasRenderingContext2D} ctx Context to render on 20146 */ 20147 _setTextStyles: function(ctx) { 20148 ctx.textBaseline = 'alphabetic'; 20149 if (!this.skipTextAlign) { 20150 ctx.textAlign = this.textAlign; 20151 } 20152 ctx.font = this._getFontDeclaration(); 20153 }, 20154 20155 /** 20156 * @private 20157 * @param {CanvasRenderingContext2D} ctx Context to render on 20158 * @return {Number} Height of fabric.Text object 20159 */ 20160 _getTextHeight: function() { 20161 return this._textLines.length * this._getHeightOfLine(); 20162 }, 20163 20164 /** 20165 * @private 20166 * @param {CanvasRenderingContext2D} ctx Context to render on 20167 * @return {Number} Maximum width of fabric.Text object 20168 */ 20169 _getTextWidth: function(ctx) { 20170 var maxWidth = this._getLineWidth(ctx, 0); 20171 20172 for (var i = 1, len = this._textLines.length; i < len; i++) { 20173 var currentLineWidth = this._getLineWidth(ctx, i); 20174 if (currentLineWidth > maxWidth) { 20175 maxWidth = currentLineWidth; 20176 } 20177 } 20178 return maxWidth; 20179 }, 20180 20181 /** 20182 * @private 20183 * @param {String} method Method name ("fillText" or "strokeText") 20184 * @param {CanvasRenderingContext2D} ctx Context to render on 20185 * @param {String} chars Chars to render 20186 * @param {Number} left Left position of text 20187 * @param {Number} top Top position of text 20188 */ 20189 _renderChars: function(method, ctx, chars, left, top) { 20190 // remove Text word from method var 20191 var shortM = method.slice(0, -4); 20192 if (this[shortM].toLive) { 20193 var offsetX = -this.width / 2 + this[shortM].offsetX || 0, 20194 offsetY = -this.height / 2 + this[shortM].offsetY || 0; 20195 ctx.save(); 20196 ctx.translate(offsetX, offsetY); 20197 left -= offsetX; 20198 top -= offsetY; 20199 } 20200 ctx[method](chars, left, top); 20201 this[shortM].toLive && ctx.restore(); 20202 }, 20203 20204 /** 20205 * @private 20206 * @param {String} method Method name ("fillText" or "strokeText") 20207 * @param {CanvasRenderingContext2D} ctx Context to render on 20208 * @param {String} line Text to render 20209 * @param {Number} left Left position of text 20210 * @param {Number} top Top position of text 20211 * @param {Number} lineIndex Index of a line in a text 20212 */ 20213 _renderTextLine: function(method, ctx, line, left, top, lineIndex) { 20214 // lift the line by quarter of fontSize 20215 top -= this.fontSize * this._fontSizeFraction; 20216 20217 // short-circuit 20218 var lineWidth = this._getLineWidth(ctx, lineIndex); 20219 if (this.textAlign !== 'justify' || this.width < lineWidth) { 20220 this._renderChars(method, ctx, line, left, top, lineIndex); 20221 return; 20222 } 20223 20224 // stretch the line 20225 var words = line.split(/\s+/), 20226 charOffset = 0, 20227 wordsWidth = this._getWidthOfWords(ctx, line, lineIndex, 0), 20228 widthDiff = this.width - wordsWidth, 20229 numSpaces = words.length - 1, 20230 spaceWidth = numSpaces > 0 ? widthDiff / numSpaces : 0, 20231 leftOffset = 0, word; 20232 20233 for (var i = 0, len = words.length; i < len; i++) { 20234 while (line[charOffset] === ' ' && charOffset < line.length) { 20235 charOffset++; 20236 } 20237 word = words[i]; 20238 this._renderChars(method, ctx, word, left + leftOffset, top, lineIndex, charOffset); 20239 leftOffset += this._getWidthOfWords(ctx, word, lineIndex, charOffset) + spaceWidth; 20240 charOffset += word.length; 20241 } 20242 }, 20243 20244 /** 20245 * @private 20246 * @param {CanvasRenderingContext2D} ctx Context to render on 20247 * @param {Number} line 20248 */ 20249 _getWidthOfWords: function (ctx, line) { 20250 return ctx.measureText(line.replace(/\s+/g, '')).width; 20251 }, 20252 20253 /** 20254 * @private 20255 * @return {Number} Left offset 20256 */ 20257 _getLeftOffset: function() { 20258 return -this.width / 2; 20259 }, 20260 20261 /** 20262 * @private 20263 * @return {Number} Top offset 20264 */ 20265 _getTopOffset: function() { 20266 return -this.height / 2; 20267 }, 20268 20269 /** 20270 * Returns true because text has no style 20271 */ 20272 isEmptyStyles: function() { 20273 return true; 20274 }, 20275 20276 /** 20277 * @private 20278 * @param {CanvasRenderingContext2D} ctx Context to render on 20279 */ 20280 _renderTextFill: function(ctx) { 20281 if (!this.fill && this.isEmptyStyles()) { 20282 return; 20283 } 20284 20285 var lineHeights = 0; 20286 20287 for (var i = 0, len = this._textLines.length; i < len; i++) { 20288 var heightOfLine = this._getHeightOfLine(ctx, i), 20289 maxHeight = heightOfLine / this.lineHeight; 20290 20291 this._renderTextLine( 20292 'fillText', 20293 ctx, 20294 this._textLines[i], 20295 this._getLeftOffset(), 20296 this._getTopOffset() + lineHeights + maxHeight, 20297 i 20298 ); 20299 lineHeights += heightOfLine; 20300 } 20301 }, 20302 20303 /** 20304 * @private 20305 * @param {CanvasRenderingContext2D} ctx Context to render on 20306 */ 20307 _renderTextStroke: function(ctx) { 20308 if ((!this.stroke || this.strokeWidth === 0) && this.isEmptyStyles()) { 20309 return; 20310 } 20311 20312 var lineHeights = 0; 20313 20314 if (this.shadow && !this.shadow.affectStroke) { 20315 this._removeShadow(ctx); 20316 } 20317 20318 ctx.save(); 20319 20320 if (this.strokeDashArray) { 20321 // Spec requires the concatenation of two copies the dash list when the number of elements is odd 20322 if (1 & this.strokeDashArray.length) { 20323 this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray); 20324 } 20325 supportsLineDash && ctx.setLineDash(this.strokeDashArray); 20326 } 20327 20328 ctx.beginPath(); 20329 for (var i = 0, len = this._textLines.length; i < len; i++) { 20330 var heightOfLine = this._getHeightOfLine(ctx, i), 20331 maxHeight = heightOfLine / this.lineHeight; 20332 20333 this._renderTextLine( 20334 'strokeText', 20335 ctx, 20336 this._textLines[i], 20337 this._getLeftOffset(), 20338 this._getTopOffset() + lineHeights + maxHeight, 20339 i 20340 ); 20341 lineHeights += heightOfLine; 20342 } 20343 ctx.closePath(); 20344 ctx.restore(); 20345 }, 20346 20347 _getHeightOfLine: function() { 20348 return this.fontSize * this._fontSizeMult * this.lineHeight; 20349 }, 20350 20351 /** 20352 * @private 20353 * @param {CanvasRenderingContext2D} ctx Context to render on 20354 * @param {Array} textLines Array of all text lines 20355 */ 20356 _renderTextBackground: function(ctx) { 20357 this._renderTextBoxBackground(ctx); 20358 this._renderTextLinesBackground(ctx); 20359 }, 20360 20361 /** 20362 * @private 20363 * @param {CanvasRenderingContext2D} ctx Context to render on 20364 */ 20365 _renderTextBoxBackground: function(ctx) { 20366 if (!this.backgroundColor) { 20367 return; 20368 } 20369 20370 ctx.fillStyle = this.backgroundColor; 20371 20372 ctx.fillRect( 20373 this._getLeftOffset(), 20374 this._getTopOffset(), 20375 this.width, 20376 this.height 20377 ); 20378 // if there is background color no other shadows 20379 // should be casted 20380 this._removeShadow(ctx); 20381 }, 20382 20383 /** 20384 * @private 20385 * @param {CanvasRenderingContext2D} ctx Context to render on 20386 */ 20387 _renderTextLinesBackground: function(ctx) { 20388 if (!this.textBackgroundColor) { 20389 return; 20390 } 20391 var lineTopOffset = 0, heightOfLine, 20392 lineWidth, lineLeftOffset; 20393 20394 ctx.fillStyle = this.textBackgroundColor; 20395 for (var i = 0, len = this._textLines.length; i < len; i++) { 20396 heightOfLine = this._getHeightOfLine(ctx, i); 20397 if (this._textLines[i] !== '') { 20398 lineWidth = this.textAlign === 'justify' ? this.width : this._getLineWidth(ctx, i); 20399 lineLeftOffset = this._getLineLeftOffset(lineWidth); 20400 ctx.fillRect( 20401 this._getLeftOffset() + lineLeftOffset, 20402 this._getTopOffset() + lineTopOffset, 20403 lineWidth, 20404 heightOfLine / this.lineHeight 20405 ); 20406 } 20407 lineTopOffset += heightOfLine; 20408 } 20409 // if there is text background color no 20410 // other shadows should be casted 20411 this._removeShadow(ctx); 20412 }, 20413 20414 /** 20415 * @private 20416 * @param {Number} lineWidth Width of text line 20417 * @return {Number} Line left offset 20418 */ 20419 _getLineLeftOffset: function(lineWidth) { 20420 if (this.textAlign === 'center') { 20421 return (this.width - lineWidth) / 2; 20422 } 20423 if (this.textAlign === 'right') { 20424 return this.width - lineWidth; 20425 } 20426 return 0; 20427 }, 20428 20429 /** 20430 * @private 20431 */ 20432 _clearCache: function() { 20433 this.__lineWidths = [ ]; 20434 this.__lineHeights = [ ]; 20435 }, 20436 20437 /** 20438 * @private 20439 */ 20440 _shouldClearCache: function() { 20441 var shouldClear = false; 20442 if (this._forceClearCache) { 20443 this._forceClearCache = false; 20444 return true; 20445 } 20446 for (var prop in this._dimensionAffectingProps) { 20447 if (this['__' + prop] !== this[prop]) { 20448 this['__' + prop] = this[prop]; 20449 shouldClear = true; 20450 } 20451 } 20452 return shouldClear; 20453 }, 20454 20455 /** 20456 * @private 20457 * @param {CanvasRenderingContext2D} ctx Context to render on 20458 * @param {Number} lineIndex line number 20459 * @return {Number} Line width 20460 */ 20461 _getLineWidth: function(ctx, lineIndex) { 20462 if (this.__lineWidths[lineIndex]) { 20463 return this.__lineWidths[lineIndex]; 20464 } 20465 var width, wordCount, line = this._textLines[lineIndex]; 20466 if (line === '') { 20467 width = 0; 20468 } 20469 else if (this.textAlign === 'justify' && this._cacheLinesWidth) { 20470 wordCount = line.split(/\s+/); 20471 //consider not justify last line, not for now. 20472 if (wordCount.length > 1) { 20473 width = this.width; 20474 } 20475 else { 20476 width = ctx.measureText(line).width; 20477 } 20478 } 20479 else { 20480 width = ctx.measureText(line).width; 20481 } 20482 this._cacheLinesWidth && (this.__lineWidths[lineIndex] = width); 20483 return width; 20484 }, 20485 20486 /** 20487 * @private 20488 * @param {CanvasRenderingContext2D} ctx Context to render on 20489 */ 20490 _renderTextDecoration: function(ctx) { 20491 if (!this.textDecoration) { 20492 return; 20493 } 20494 20495 var halfOfVerticalBox = this.height / 2, 20496 _this = this, offsets = []; 20497 20498 /** @ignore */ 20499 function renderLinesAtOffset(offsets) { 20500 var i, lineHeight = 0, len, j, oLen, lineWidth, 20501 lineLeftOffset, heightOfLine; 20502 20503 for (i = 0, len = _this._textLines.length; i < len; i++) { 20504 20505 lineWidth = _this._getLineWidth(ctx, i), 20506 lineLeftOffset = _this._getLineLeftOffset(lineWidth), 20507 heightOfLine = _this._getHeightOfLine(ctx, i); 20508 20509 for (j = 0, oLen = offsets.length; j < oLen; j++) { 20510 ctx.fillRect( 20511 _this._getLeftOffset() + lineLeftOffset, 20512 lineHeight + (_this._fontSizeMult - 1 + offsets[j] ) * _this.fontSize - halfOfVerticalBox, 20513 lineWidth, 20514 _this.fontSize / 15); 20515 } 20516 lineHeight += heightOfLine; 20517 } 20518 } 20519 20520 if (this.textDecoration.indexOf('underline') > -1) { 20521 offsets.push(0.85); // 1 - 3/16 20522 } 20523 if (this.textDecoration.indexOf('line-through') > -1) { 20524 offsets.push(0.43); 20525 } 20526 if (this.textDecoration.indexOf('overline') > -1) { 20527 offsets.push(-0.12); 20528 } 20529 if (offsets.length > 0) { 20530 renderLinesAtOffset(offsets); 20531 } 20532 }, 20533 20534 /** 20535 * @private 20536 */ 20537 _getFontDeclaration: function() { 20538 return [ 20539 // node-canvas needs "weight style", while browsers need "style weight" 20540 (fabric.isLikelyNode ? this.fontWeight : this.fontStyle), 20541 (fabric.isLikelyNode ? this.fontStyle : this.fontWeight), 20542 this.fontSize + 'px', 20543 (fabric.isLikelyNode ? ('"' + this.fontFamily + '"') : this.fontFamily) 20544 ].join(' '); 20545 }, 20546 20547 /** 20548 * Renders text instance on a specified context 20549 * @param {CanvasRenderingContext2D} ctx Context to render on 20550 */ 20551 render: function(ctx, noTransform) { 20552 // do not render if object is not visible 20553 if (!this.visible) { 20554 return; 20555 } 20556 20557 ctx.save(); 20558 this._setTextStyles(ctx); 20559 20560 if (this._shouldClearCache()) { 20561 this._initDimensions(ctx); 20562 } 20563 if (!noTransform) { 20564 this.transform(ctx); 20565 } 20566 if (this.transformMatrix) { 20567 ctx.transform.apply(ctx, this.transformMatrix); 20568 } 20569 if (this.group && this.group.type === 'path-group') { 20570 ctx.translate(this.left, this.top); 20571 } 20572 this._render(ctx); 20573 ctx.restore(); 20574 }, 20575 20576 /** 20577 * Returns the text as an array of lines. 20578 * @returns {Array} Lines in the text 20579 */ 20580 _splitTextIntoLines: function() { 20581 return this.text.split(this._reNewline); 20582 }, 20583 20584 /** 20585 * Returns object representation of an instance 20586 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 20587 * @return {Object} Object representation of an instance 20588 */ 20589 toObject: function(propertiesToInclude) { 20590 var object = extend(this.callSuper('toObject', propertiesToInclude), { 20591 text: this.text, 20592 fontSize: this.fontSize, 20593 fontWeight: this.fontWeight, 20594 fontFamily: this.fontFamily, 20595 fontStyle: this.fontStyle, 20596 lineHeight: this.lineHeight, 20597 textDecoration: this.textDecoration, 20598 textAlign: this.textAlign, 20599 textBackgroundColor: this.textBackgroundColor 20600 }); 20601 if (!this.includeDefaultValues) { 20602 this._removeDefaultValues(object); 20603 } 20604 return object; 20605 }, 20606 20607 /* _TO_SVG_START_ */ 20608 /** 20609 * Returns SVG representation of an instance 20610 * @param {Function} [reviver] Method for further parsing of svg representation. 20611 * @return {String} svg representation of an instance 20612 */ 20613 toSVG: function(reviver) { 20614 var markup = this._createBaseSVGMarkup(), 20615 offsets = this._getSVGLeftTopOffsets(this.ctx), 20616 textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft); 20617 this._wrapSVGTextAndBg(markup, textAndBg); 20618 20619 return reviver ? reviver(markup.join('')) : markup.join(''); 20620 }, 20621 20622 /** 20623 * @private 20624 */ 20625 _getSVGLeftTopOffsets: function(ctx) { 20626 var lineTop = this._getHeightOfLine(ctx, 0), 20627 textLeft = -this.width / 2, 20628 textTop = 0; 20629 20630 return { 20631 textLeft: textLeft + (this.group && this.group.type === 'path-group' ? this.left : 0), 20632 textTop: textTop + (this.group && this.group.type === 'path-group' ? -this.top : 0), 20633 lineTop: lineTop 20634 }; 20635 }, 20636 20637 /** 20638 * @private 20639 */ 20640 _wrapSVGTextAndBg: function(markup, textAndBg) { 20641 var noShadow = true, filter = this.getSvgFilter(), 20642 style = filter === '' ? '' : ' style="' + filter + '"'; 20643 20644 markup.push( 20645 '\t<g transform="', this.getSvgTransform(), this.getSvgTransformMatrix(), '"', 20646 style, '>\n', 20647 textAndBg.textBgRects.join(''), 20648 '\t\t<text ', 20649 (this.fontFamily ? 'font-family="' + this.fontFamily.replace(/"/g, '\'') + '" ': ''), 20650 (this.fontSize ? 'font-size="' + this.fontSize + '" ': ''), 20651 (this.fontStyle ? 'font-style="' + this.fontStyle + '" ': ''), 20652 (this.fontWeight ? 'font-weight="' + this.fontWeight + '" ': ''), 20653 (this.textDecoration ? 'text-decoration="' + this.textDecoration + '" ': ''), 20654 'style="', this.getSvgStyles(noShadow), '" >\n', 20655 textAndBg.textSpans.join(''), 20656 '\t\t</text>\n', 20657 '\t</g>\n' 20658 ); 20659 }, 20660 20661 /** 20662 * @private 20663 * @param {Number} textTopOffset Text top offset 20664 * @param {Number} textLeftOffset Text left offset 20665 * @return {Object} 20666 */ 20667 _getSVGTextAndBg: function(textTopOffset, textLeftOffset) { 20668 var textSpans = [ ], 20669 textBgRects = [ ], 20670 height = 0; 20671 // bounding-box background 20672 this._setSVGBg(textBgRects); 20673 20674 // text and text-background 20675 for (var i = 0, len = this._textLines.length; i < len; i++) { 20676 if (this.textBackgroundColor) { 20677 this._setSVGTextLineBg(textBgRects, i, textLeftOffset, textTopOffset, height); 20678 } 20679 this._setSVGTextLineText(i, textSpans, height, textLeftOffset, textTopOffset, textBgRects); 20680 height += this._getHeightOfLine(this.ctx, i); 20681 } 20682 20683 return { 20684 textSpans: textSpans, 20685 textBgRects: textBgRects 20686 }; 20687 }, 20688 20689 _setSVGTextLineText: function(i, textSpans, height, textLeftOffset, textTopOffset) { 20690 var yPos = this.fontSize * (this._fontSizeMult - this._fontSizeFraction) 20691 - textTopOffset + height - this.height / 2; 20692 if (this.textAlign === 'justify') { 20693 // i call from here to do not intefere with IText 20694 this._setSVGTextLineJustifed(i, textSpans, yPos, textLeftOffset); 20695 return; 20696 } 20697 textSpans.push( 20698 '\t\t\t<tspan x="', 20699 toFixed(textLeftOffset + this._getLineLeftOffset(this._getLineWidth(this.ctx, i)), NUM_FRACTION_DIGITS), '" ', 20700 'y="', 20701 toFixed(yPos, NUM_FRACTION_DIGITS), 20702 '" ', 20703 // doing this on <tspan> elements since setting opacity 20704 // on containing <text> one doesn't work in Illustrator 20705 this._getFillAttributes(this.fill), '>', 20706 fabric.util.string.escapeXml(this._textLines[i]), 20707 '</tspan>\n' 20708 ); 20709 }, 20710 20711 _setSVGTextLineJustifed: function(i, textSpans, yPos, textLeftOffset) { 20712 var ctx = fabric.util.createCanvasElement().getContext('2d'); 20713 20714 this._setTextStyles(ctx); 20715 20716 var line = this._textLines[i], 20717 words = line.split(/\s+/), 20718 wordsWidth = this._getWidthOfWords(ctx, line), 20719 widthDiff = this.width - wordsWidth, 20720 numSpaces = words.length - 1, 20721 spaceWidth = numSpaces > 0 ? widthDiff / numSpaces : 0, 20722 word, attributes = this._getFillAttributes(this.fill), 20723 len; 20724 20725 textLeftOffset += this._getLineLeftOffset(this._getLineWidth(ctx, i)); 20726 20727 for (i = 0, len = words.length; i < len; i++) { 20728 word = words[i]; 20729 textSpans.push( 20730 '\t\t\t<tspan x="', 20731 toFixed(textLeftOffset, NUM_FRACTION_DIGITS), '" ', 20732 'y="', 20733 toFixed(yPos, NUM_FRACTION_DIGITS), 20734 '" ', 20735 // doing this on <tspan> elements since setting opacity 20736 // on containing <text> one doesn't work in Illustrator 20737 attributes, '>', 20738 fabric.util.string.escapeXml(word), 20739 '</tspan>\n' 20740 ); 20741 textLeftOffset += this._getWidthOfWords(ctx, word) + spaceWidth; 20742 } 20743 }, 20744 20745 _setSVGTextLineBg: function(textBgRects, i, textLeftOffset, textTopOffset, height) { 20746 textBgRects.push( 20747 '\t\t<rect ', 20748 this._getFillAttributes(this.textBackgroundColor), 20749 ' x="', 20750 toFixed(textLeftOffset + this._getLineLeftOffset(this._getLineWidth(this.ctx, i)), NUM_FRACTION_DIGITS), 20751 '" y="', 20752 toFixed(height - this.height / 2, NUM_FRACTION_DIGITS), 20753 '" width="', 20754 toFixed(this._getLineWidth(this.ctx, i), NUM_FRACTION_DIGITS), 20755 '" height="', 20756 toFixed(this._getHeightOfLine(this.ctx, i) / this.lineHeight, NUM_FRACTION_DIGITS), 20757 '"></rect>\n'); 20758 }, 20759 20760 _setSVGBg: function(textBgRects) { 20761 if (this.backgroundColor) { 20762 textBgRects.push( 20763 '\t\t<rect ', 20764 this._getFillAttributes(this.backgroundColor), 20765 ' x="', 20766 toFixed(-this.width / 2, NUM_FRACTION_DIGITS), 20767 '" y="', 20768 toFixed(-this.height / 2, NUM_FRACTION_DIGITS), 20769 '" width="', 20770 toFixed(this.width, NUM_FRACTION_DIGITS), 20771 '" height="', 20772 toFixed(this.height, NUM_FRACTION_DIGITS), 20773 '"></rect>\n'); 20774 } 20775 }, 20776 20777 /** 20778 * Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values 20779 * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1 20780 * 20781 * @private 20782 * @param {Any} value 20783 * @return {String} 20784 */ 20785 _getFillAttributes: function(value) { 20786 var fillColor = (value && typeof value === 'string') ? new fabric.Color(value) : ''; 20787 if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) { 20788 return 'fill="' + value + '"'; 20789 } 20790 return 'opacity="' + fillColor.getAlpha() + '" fill="' + fillColor.setAlpha(1).toRgb() + '"'; 20791 }, 20792 /* _TO_SVG_END_ */ 20793 20794 /** 20795 * Sets specified property to a specified value 20796 * @param {String} key 20797 * @param {Any} value 20798 * @return {fabric.Text} thisArg 20799 * @chainable 20800 */ 20801 _set: function(key, value) { 20802 this.callSuper('_set', key, value); 20803 20804 if (key in this._dimensionAffectingProps) { 20805 this._initDimensions(); 20806 this.setCoords(); 20807 } 20808 }, 20809 20810 /** 20811 * Returns complexity of an instance 20812 * @return {Number} complexity 20813 */ 20814 complexity: function() { 20815 return 1; 20816 } 20817 }); 20818 20819 /* _FROM_SVG_START_ */ 20820 /** 20821 * List of attribute names to account for when parsing SVG element (used by {@link fabric.Text.fromElement}) 20822 * @static 20823 * @memberOf fabric.Text 20824 * @see: http://www.w3.org/TR/SVG/text.html#TextElement 20825 */ 20826 fabric.Text.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat( 20827 'x y dx dy font-family font-style font-weight font-size text-decoration text-anchor'.split(' ')); 20828 20829 /** 20830 * Default SVG font size 20831 * @static 20832 * @memberOf fabric.Text 20833 */ 20834 fabric.Text.DEFAULT_SVG_FONT_SIZE = 16; 20835 20836 /** 20837 * Returns fabric.Text instance from an SVG element (<b>not yet implemented</b>) 20838 * @static 20839 * @memberOf fabric.Text 20840 * @param {SVGElement} element Element to parse 20841 * @param {Object} [options] Options object 20842 * @return {fabric.Text} Instance of fabric.Text 20843 */ 20844 fabric.Text.fromElement = function(element, options) { 20845 if (!element) { 20846 return null; 20847 } 20848 20849 var parsedAttributes = fabric.parseAttributes(element, fabric.Text.ATTRIBUTE_NAMES); 20850 options = fabric.util.object.extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes); 20851 20852 options.top = options.top || 0; 20853 options.left = options.left || 0; 20854 if ('dx' in parsedAttributes) { 20855 options.left += parsedAttributes.dx; 20856 } 20857 if ('dy' in parsedAttributes) { 20858 options.top += parsedAttributes.dy; 20859 } 20860 if (!('fontSize' in options)) { 20861 options.fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE; 20862 } 20863 20864 if (!options.originX) { 20865 options.originX = 'left'; 20866 } 20867 20868 var textContent = ''; 20869 20870 // The XML is not properly parsed in IE9 so a workaround to get 20871 // textContent is through firstChild.data. Another workaround would be 20872 // to convert XML loaded from a file to be converted using DOMParser (same way loadSVGFromString() does) 20873 if (!('textContent' in element)) { 20874 if ('firstChild' in element && element.firstChild !== null) { 20875 if ('data' in element.firstChild && element.firstChild.data !== null) { 20876 textContent = element.firstChild.data; 20877 } 20878 } 20879 } 20880 else { 20881 textContent = element.textContent; 20882 } 20883 20884 textContent = textContent.replace(/^\s+|\s+$|\n+/g, '').replace(/\s+/g, ' '); 20885 20886 var text = new fabric.Text(textContent, options), 20887 /* 20888 Adjust positioning: 20889 x/y attributes in SVG correspond to the bottom-left corner of text bounding box 20890 top/left properties in Fabric correspond to center point of text bounding box 20891 */ 20892 offX = 0; 20893 20894 if (text.originX === 'left') { 20895 offX = text.getWidth() / 2; 20896 } 20897 if (text.originX === 'right') { 20898 offX = -text.getWidth() / 2; 20899 } 20900 text.set({ 20901 left: text.getLeft() + offX, 20902 top: text.getTop() - text.getHeight() / 2 + text.fontSize * (0.18 + text._fontSizeFraction) /* 0.3 is the old lineHeight */ 20903 }); 20904 20905 return text; 20906 }; 20907 /* _FROM_SVG_END_ */ 20908 20909 /** 20910 * Returns fabric.Text instance from an object representation 20911 * @static 20912 * @memberOf fabric.Text 20913 * @param {Object} object Object to create an instance from 20914 * @return {fabric.Text} Instance of fabric.Text 20915 */ 20916 fabric.Text.fromObject = function(object) { 20917 return new fabric.Text(object.text, clone(object)); 20918 }; 20919 20920 fabric.util.createAccessors(fabric.Text); 20921 20922 })(typeof exports !== 'undefined' ? exports : this); 20923 20924 20925 (function() { 20926 20927 var clone = fabric.util.object.clone; 20928 20929 /** 20930 * IText class (introduced in <b>v1.4</b>) Events are also fired with "text:" 20931 * prefix when observing canvas. 20932 * @class fabric.IText 20933 * @extends fabric.Text 20934 * @mixes fabric.Observable 20935 * 20936 * @fires changed 20937 * @fires selection:changed 20938 * @fires editing:entered 20939 * @fires editing:exited 20940 * 20941 * @return {fabric.IText} thisArg 20942 * @see {@link fabric.IText#initialize} for constructor definition 20943 * 20944 * <p>Supported key combinations:</p> 20945 * <pre> 20946 * Move cursor: left, right, up, down 20947 * Select character: shift + left, shift + right 20948 * Select text vertically: shift + up, shift + down 20949 * Move cursor by word: alt + left, alt + right 20950 * Select words: shift + alt + left, shift + alt + right 20951 * Move cursor to line start/end: cmd + left, cmd + right or home, end 20952 * Select till start/end of line: cmd + shift + left, cmd + shift + right or shift + home, shift + end 20953 * Jump to start/end of text: cmd + up, cmd + down 20954 * Select till start/end of text: cmd + shift + up, cmd + shift + down or shift + pgUp, shift + pgDown 20955 * Delete character: backspace 20956 * Delete word: alt + backspace 20957 * Delete line: cmd + backspace 20958 * Forward delete: delete 20959 * Copy text: ctrl/cmd + c 20960 * Paste text: ctrl/cmd + v 20961 * Cut text: ctrl/cmd + x 20962 * Select entire text: ctrl/cmd + a 20963 * Quit editing tab or esc 20964 * </pre> 20965 * 20966 * <p>Supported mouse/touch combination</p> 20967 * <pre> 20968 * Position cursor: click/touch 20969 * Create selection: click/touch & drag 20970 * Create selection: click & shift + click 20971 * Select word: double click 20972 * Select line: triple click 20973 * </pre> 20974 */ 20975 fabric.IText = fabric.util.createClass(fabric.Text, fabric.Observable, /** @lends fabric.IText.prototype */ { 20976 20977 /** 20978 * Type of an object 20979 * @type String 20980 * @default 20981 */ 20982 type: 'i-text', 20983 20984 /** 20985 * Index where text selection starts (or where cursor is when there is no selection) 20986 * @type Number 20987 * @default 20988 */ 20989 selectionStart: 0, 20990 20991 /** 20992 * Index where text selection ends 20993 * @type Number 20994 * @default 20995 */ 20996 selectionEnd: 0, 20997 20998 /** 20999 * Color of text selection 21000 * @type String 21001 * @default 21002 */ 21003 selectionColor: 'rgba(17,119,255,0.3)', 21004 21005 /** 21006 * Indicates whether text is in editing mode 21007 * @type Boolean 21008 * @default 21009 */ 21010 isEditing: false, 21011 21012 /** 21013 * Indicates whether a text can be edited 21014 * @type Boolean 21015 * @default 21016 */ 21017 editable: true, 21018 21019 /** 21020 * Border color of text object while it's in editing mode 21021 * @type String 21022 * @default 21023 */ 21024 editingBorderColor: 'rgba(102,153,255,0.25)', 21025 21026 /** 21027 * Width of cursor (in px) 21028 * @type Number 21029 * @default 21030 */ 21031 cursorWidth: 2, 21032 21033 /** 21034 * Color of default cursor (when not overwritten by character style) 21035 * @type String 21036 * @default 21037 */ 21038 cursorColor: '#333', 21039 21040 /** 21041 * Delay between cursor blink (in ms) 21042 * @type Number 21043 * @default 21044 */ 21045 cursorDelay: 1000, 21046 21047 /** 21048 * Duration of cursor fadein (in ms) 21049 * @type Number 21050 * @default 21051 */ 21052 cursorDuration: 600, 21053 21054 /** 21055 * Object containing character styles 21056 * (where top-level properties corresponds to line number and 2nd-level properties -- to char number in a line) 21057 * @type Object 21058 * @default 21059 */ 21060 styles: null, 21061 21062 /** 21063 * Indicates whether internal text char widths can be cached 21064 * @type Boolean 21065 * @default 21066 */ 21067 caching: true, 21068 21069 /** 21070 * @private 21071 */ 21072 _reSpace: /\s|\n/, 21073 21074 /** 21075 * @private 21076 */ 21077 _currentCursorOpacity: 0, 21078 21079 /** 21080 * @private 21081 */ 21082 _selectionDirection: null, 21083 21084 /** 21085 * @private 21086 */ 21087 _abortCursorAnimation: false, 21088 21089 /** 21090 * @private 21091 */ 21092 _charWidthsCache: { }, 21093 21094 /** 21095 * @private 21096 */ 21097 __widthOfSpace: [ ], 21098 21099 /** 21100 * Constructor 21101 * @param {String} text Text string 21102 * @param {Object} [options] Options object 21103 * @return {fabric.IText} thisArg 21104 */ 21105 initialize: function(text, options) { 21106 this.styles = options ? (options.styles || { }) : { }; 21107 this.callSuper('initialize', text, options); 21108 this.initBehavior(); 21109 }, 21110 21111 /** 21112 * @private 21113 */ 21114 _clearCache: function() { 21115 this.callSuper('_clearCache'); 21116 this.__widthOfSpace = [ ]; 21117 }, 21118 21119 /** 21120 * Returns true if object has no styling 21121 */ 21122 isEmptyStyles: function() { 21123 if (!this.styles) { 21124 return true; 21125 } 21126 var obj = this.styles; 21127 21128 for (var p1 in obj) { 21129 for (var p2 in obj[p1]) { 21130 /*jshint unused:false */ 21131 for (var p3 in obj[p1][p2]) { 21132 return false; 21133 } 21134 } 21135 } 21136 return true; 21137 }, 21138 21139 /** 21140 * Sets selection start (left boundary of a selection) 21141 * @param {Number} index Index to set selection start to 21142 */ 21143 setSelectionStart: function(index) { 21144 index = Math.max(index, 0); 21145 if (this.selectionStart !== index) { 21146 this.fire('selection:changed'); 21147 this.canvas && this.canvas.fire('text:selection:changed', { target: this }); 21148 this.selectionStart = index; 21149 } 21150 this._updateTextarea(); 21151 }, 21152 21153 /** 21154 * Sets selection end (right boundary of a selection) 21155 * @param {Number} index Index to set selection end to 21156 */ 21157 setSelectionEnd: function(index) { 21158 index = Math.min(index, this.text.length); 21159 if (this.selectionEnd !== index) { 21160 this.fire('selection:changed'); 21161 this.canvas && this.canvas.fire('text:selection:changed', { target: this }); 21162 this.selectionEnd = index; 21163 } 21164 this._updateTextarea(); 21165 }, 21166 21167 /** 21168 * Gets style of a current selection/cursor (at the start position) 21169 * @param {Number} [startIndex] Start index to get styles at 21170 * @param {Number} [endIndex] End index to get styles at 21171 * @return {Object} styles Style object at a specified (or current) index 21172 */ 21173 getSelectionStyles: function(startIndex, endIndex) { 21174 21175 if (arguments.length === 2) { 21176 var styles = [ ]; 21177 for (var i = startIndex; i < endIndex; i++) { 21178 styles.push(this.getSelectionStyles(i)); 21179 } 21180 return styles; 21181 } 21182 21183 var loc = this.get2DCursorLocation(startIndex), 21184 style = this._getStyleDeclaration(loc.lineIndex, loc.charIndex); 21185 21186 return style || {}; 21187 }, 21188 21189 /** 21190 * Sets style of a current selection 21191 * @param {Object} [styles] Styles object 21192 * @return {fabric.IText} thisArg 21193 * @chainable 21194 */ 21195 setSelectionStyles: function(styles) { 21196 if (this.selectionStart === this.selectionEnd) { 21197 this._extendStyles(this.selectionStart, styles); 21198 } 21199 else { 21200 for (var i = this.selectionStart; i < this.selectionEnd; i++) { 21201 this._extendStyles(i, styles); 21202 } 21203 } 21204 /* not included in _extendStyles to avoid clearing cache more than once */ 21205 this._forceClearCache = true; 21206 return this; 21207 }, 21208 21209 /** 21210 * @private 21211 */ 21212 _extendStyles: function(index, styles) { 21213 var loc = this.get2DCursorLocation(index); 21214 21215 if (!this._getLineStyle(loc.lineIndex)) { 21216 this._setLineStyle(loc.lineIndex, {}); 21217 } 21218 21219 if (!this._getStyleDeclaration(loc.lineIndex, loc.charIndex)) { 21220 this._setStyleDeclaration(loc.lineIndex, loc.charIndex, {}); 21221 } 21222 21223 fabric.util.object.extend(this._getStyleDeclaration(loc.lineIndex, loc.charIndex), styles); 21224 }, 21225 21226 /** 21227 * @private 21228 * @param {CanvasRenderingContext2D} ctx Context to render on 21229 */ 21230 _render: function(ctx) { 21231 this.callSuper('_render', ctx); 21232 this.ctx = ctx; 21233 this.isEditing && this.renderCursorOrSelection(); 21234 }, 21235 21236 /** 21237 * Renders cursor or selection (depending on what exists) 21238 */ 21239 renderCursorOrSelection: function() { 21240 if (!this.active) { 21241 return; 21242 } 21243 21244 var chars = this.text.split(''), 21245 boundaries, ctx; 21246 21247 if (this.canvas.contextTop) { 21248 ctx = this.canvas.contextTop; 21249 ctx.save(); 21250 ctx.transform.apply(ctx, this.canvas.viewportTransform); 21251 this.transform(ctx); 21252 this.transformMatrix && ctx.transform.apply(ctx, this.transformMatrix); 21253 } 21254 else { 21255 ctx = this.ctx; 21256 ctx.save(); 21257 } 21258 21259 if (this.selectionStart === this.selectionEnd) { 21260 boundaries = this._getCursorBoundaries(chars, 'cursor'); 21261 this.renderCursor(boundaries, ctx); 21262 } 21263 else { 21264 boundaries = this._getCursorBoundaries(chars, 'selection'); 21265 this.renderSelection(chars, boundaries, ctx); 21266 } 21267 21268 ctx.restore(); 21269 }, 21270 21271 /** 21272 * Returns 2d representation (lineIndex and charIndex) of cursor (or selection start) 21273 * @param {Number} [selectionStart] Optional index. When not given, current selectionStart is used. 21274 */ 21275 get2DCursorLocation: function(selectionStart) { 21276 if (typeof selectionStart === 'undefined') { 21277 selectionStart = this.selectionStart; 21278 } 21279 var len = this._textLines.length; 21280 for (var i = 0; i < len; i++) { 21281 if (selectionStart <= this._textLines[i].length) { 21282 return { 21283 lineIndex: i, 21284 charIndex: selectionStart 21285 }; 21286 } 21287 selectionStart -= this._textLines[i].length + 1; 21288 } 21289 return { 21290 lineIndex: i - 1, 21291 charIndex: this._textLines[i - 1].length < selectionStart ? this._textLines[i - 1].length : selectionStart 21292 }; 21293 }, 21294 21295 /** 21296 * Returns complete style of char at the current cursor 21297 * @param {Number} lineIndex Line index 21298 * @param {Number} charIndex Char index 21299 * @return {Object} Character style 21300 */ 21301 getCurrentCharStyle: function(lineIndex, charIndex) { 21302 var style = this._getStyleDeclaration(lineIndex, charIndex === 0 ? 0 : charIndex - 1); 21303 21304 return { 21305 fontSize: style && style.fontSize || this.fontSize, 21306 fill: style && style.fill || this.fill, 21307 textBackgroundColor: style && style.textBackgroundColor || this.textBackgroundColor, 21308 textDecoration: style && style.textDecoration || this.textDecoration, 21309 fontFamily: style && style.fontFamily || this.fontFamily, 21310 fontWeight: style && style.fontWeight || this.fontWeight, 21311 fontStyle: style && style.fontStyle || this.fontStyle, 21312 stroke: style && style.stroke || this.stroke, 21313 strokeWidth: style && style.strokeWidth || this.strokeWidth 21314 }; 21315 }, 21316 21317 /** 21318 * Returns fontSize of char at the current cursor 21319 * @param {Number} lineIndex Line index 21320 * @param {Number} charIndex Char index 21321 * @return {Number} Character font size 21322 */ 21323 getCurrentCharFontSize: function(lineIndex, charIndex) { 21324 var style = this._getStyleDeclaration(lineIndex, charIndex === 0 ? 0 : charIndex - 1); 21325 return style && style.fontSize ? style.fontSize : this.fontSize; 21326 }, 21327 21328 /** 21329 * Returns color (fill) of char at the current cursor 21330 * @param {Number} lineIndex Line index 21331 * @param {Number} charIndex Char index 21332 * @return {String} Character color (fill) 21333 */ 21334 getCurrentCharColor: function(lineIndex, charIndex) { 21335 var style = this._getStyleDeclaration(lineIndex, charIndex === 0 ? 0 : charIndex - 1); 21336 return style && style.fill ? style.fill : this.cursorColor; 21337 }, 21338 21339 /** 21340 * Returns cursor boundaries (left, top, leftOffset, topOffset) 21341 * @private 21342 * @param {Array} chars Array of characters 21343 * @param {String} typeOfBoundaries 21344 */ 21345 _getCursorBoundaries: function(chars, typeOfBoundaries) { 21346 21347 // left/top are left/top of entire text box 21348 // leftOffset/topOffset are offset from that left/top point of a text box 21349 21350 var left = Math.round(this._getLeftOffset()), 21351 top = this._getTopOffset(), 21352 21353 offsets = this._getCursorBoundariesOffsets( 21354 chars, typeOfBoundaries); 21355 21356 return { 21357 left: left, 21358 top: top, 21359 leftOffset: offsets.left + offsets.lineLeft, 21360 topOffset: offsets.top 21361 }; 21362 }, 21363 21364 /** 21365 * @private 21366 */ 21367 _getCursorBoundariesOffsets: function(chars, typeOfBoundaries) { 21368 21369 var lineLeftOffset = 0, 21370 21371 lineIndex = 0, 21372 charIndex = 0, 21373 topOffset = 0, 21374 leftOffset = 0; 21375 21376 for (var i = 0; i < this.selectionStart; i++) { 21377 if (chars[i] === '\n') { 21378 leftOffset = 0; 21379 topOffset += this._getHeightOfLine(this.ctx, lineIndex); 21380 21381 lineIndex++; 21382 charIndex = 0; 21383 } 21384 else { 21385 leftOffset += this._getWidthOfChar(this.ctx, chars[i], lineIndex, charIndex); 21386 charIndex++; 21387 } 21388 21389 lineLeftOffset = this._getLineLeftOffset(this._getLineWidth(this.ctx, lineIndex)); 21390 } 21391 if (typeOfBoundaries === 'cursor') { 21392 topOffset += (1 - this._fontSizeFraction) * this._getHeightOfLine(this.ctx, lineIndex) / this.lineHeight 21393 - this.getCurrentCharFontSize(lineIndex, charIndex) * (1 - this._fontSizeFraction); 21394 } 21395 21396 return { 21397 top: topOffset, 21398 left: leftOffset, 21399 lineLeft: lineLeftOffset 21400 }; 21401 }, 21402 21403 /** 21404 * Renders cursor 21405 * @param {Object} boundaries 21406 * @param {CanvasRenderingContext2D} ctx transformed context to draw on 21407 */ 21408 renderCursor: function(boundaries, ctx) { 21409 21410 var cursorLocation = this.get2DCursorLocation(), 21411 lineIndex = cursorLocation.lineIndex, 21412 charIndex = cursorLocation.charIndex, 21413 charHeight = this.getCurrentCharFontSize(lineIndex, charIndex), 21414 leftOffset = (lineIndex === 0 && charIndex === 0) 21415 ? this._getLineLeftOffset(this._getLineWidth(ctx, lineIndex)) 21416 : boundaries.leftOffset; 21417 21418 ctx.fillStyle = this.getCurrentCharColor(lineIndex, charIndex); 21419 ctx.globalAlpha = this.__isMousedown ? 1 : this._currentCursorOpacity; 21420 21421 ctx.fillRect( 21422 boundaries.left + leftOffset, 21423 boundaries.top + boundaries.topOffset, 21424 this.cursorWidth / this.scaleX, 21425 charHeight); 21426 21427 }, 21428 21429 /** 21430 * Renders text selection 21431 * @param {Array} chars Array of characters 21432 * @param {Object} boundaries Object with left/top/leftOffset/topOffset 21433 * @param {CanvasRenderingContext2D} ctx transformed context to draw on 21434 */ 21435 renderSelection: function(chars, boundaries, ctx) { 21436 21437 ctx.fillStyle = this.selectionColor; 21438 21439 var start = this.get2DCursorLocation(this.selectionStart), 21440 end = this.get2DCursorLocation(this.selectionEnd), 21441 startLine = start.lineIndex, 21442 endLine = end.lineIndex; 21443 21444 for (var i = startLine; i <= endLine; i++) { 21445 var lineOffset = this._getLineLeftOffset(this._getLineWidth(ctx, i)) || 0, 21446 lineHeight = this._getHeightOfLine(this.ctx, i), 21447 boxWidth = 0, line = this._textLines[i]; 21448 21449 if (i === startLine) { 21450 for (var j = 0, len = line.length; j < len; j++) { 21451 if (j >= start.charIndex && (i !== endLine || j < end.charIndex)) { 21452 boxWidth += this._getWidthOfChar(ctx, line[j], i, j); 21453 } 21454 if (j < start.charIndex) { 21455 lineOffset += this._getWidthOfChar(ctx, line[j], i, j); 21456 } 21457 } 21458 } 21459 else if (i > startLine && i < endLine) { 21460 boxWidth += this._getLineWidth(ctx, i) || 5; 21461 } 21462 else if (i === endLine) { 21463 for (var j2 = 0, j2len = end.charIndex; j2 < j2len; j2++) { 21464 boxWidth += this._getWidthOfChar(ctx, line[j2], i, j2); 21465 } 21466 } 21467 21468 ctx.fillRect( 21469 boundaries.left + lineOffset, 21470 boundaries.top + boundaries.topOffset, 21471 boxWidth, 21472 lineHeight); 21473 21474 boundaries.topOffset += lineHeight; 21475 } 21476 }, 21477 21478 /** 21479 * @private 21480 * @param {String} method 21481 * @param {CanvasRenderingContext2D} ctx Context to render on 21482 */ 21483 _renderChars: function(method, ctx, line, left, top, lineIndex, charOffset) { 21484 21485 if (this.isEmptyStyles()) { 21486 return this._renderCharsFast(method, ctx, line, left, top); 21487 } 21488 21489 charOffset = charOffset || 0; 21490 this.skipTextAlign = true; 21491 21492 // set proper box offset 21493 left -= this.textAlign === 'center' 21494 ? (this.width / 2) 21495 : (this.textAlign === 'right') 21496 ? this.width 21497 : 0; 21498 21499 // set proper line offset 21500 var lineHeight = this._getHeightOfLine(ctx, lineIndex), 21501 lineLeftOffset = this._getLineLeftOffset(this._getLineWidth(ctx, lineIndex)), 21502 prevStyle, 21503 thisStyle, 21504 charsToRender = ''; 21505 21506 left += lineLeftOffset || 0; 21507 21508 ctx.save(); 21509 top -= lineHeight / this.lineHeight * this._fontSizeFraction; 21510 for (var i = charOffset, len = line.length + charOffset; i <= len; i++) { 21511 prevStyle = prevStyle || this.getCurrentCharStyle(lineIndex, i); 21512 thisStyle = this.getCurrentCharStyle(lineIndex, i + 1); 21513 21514 if (this._hasStyleChanged(prevStyle, thisStyle) || i === len) { 21515 this._renderChar(method, ctx, lineIndex, i - 1, charsToRender, left, top, lineHeight); 21516 charsToRender = ''; 21517 prevStyle = thisStyle; 21518 } 21519 charsToRender += line[i - charOffset]; 21520 } 21521 ctx.restore(); 21522 }, 21523 21524 /** 21525 * @private 21526 * @param {String} method 21527 * @param {CanvasRenderingContext2D} ctx Context to render on 21528 * @param {String} line Content of the line 21529 * @param {Number} left Left coordinate 21530 * @param {Number} top Top coordinate 21531 */ 21532 _renderCharsFast: function(method, ctx, line, left, top) { 21533 this.skipTextAlign = false; 21534 21535 if (method === 'fillText' && this.fill) { 21536 this.callSuper('_renderChars', method, ctx, line, left, top); 21537 } 21538 if (method === 'strokeText' && ((this.stroke && this.strokeWidth > 0) || this.skipFillStrokeCheck)) { 21539 this.callSuper('_renderChars', method, ctx, line, left, top); 21540 } 21541 }, 21542 21543 /** 21544 * @private 21545 * @param {String} method 21546 * @param {CanvasRenderingContext2D} ctx Context to render on 21547 * @param {Number} lineIndex 21548 * @param {Number} i 21549 * @param {String} _char 21550 * @param {Number} left Left coordinate 21551 * @param {Number} top Top coordinate 21552 * @param {Number} lineHeight Height of the line 21553 */ 21554 _renderChar: function(method, ctx, lineIndex, i, _char, left, top, lineHeight) { 21555 var charWidth, charHeight, shouldFill, shouldStroke, 21556 decl = this._getStyleDeclaration(lineIndex, i), 21557 offset, textDecoration; 21558 21559 if (decl) { 21560 charHeight = this._getHeightOfChar(ctx, _char, lineIndex, i); 21561 shouldStroke = decl.stroke; 21562 shouldFill = decl.fill; 21563 textDecoration = decl.textDecoration; 21564 } 21565 else { 21566 charHeight = this.fontSize; 21567 } 21568 21569 shouldStroke = (shouldStroke || this.stroke) && method === 'strokeText'; 21570 shouldFill = (shouldFill || this.fill) && method === 'fillText'; 21571 21572 decl && ctx.save(); 21573 21574 charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i, decl || {}); 21575 textDecoration = textDecoration || this.textDecoration; 21576 21577 if (decl && decl.textBackgroundColor) { 21578 this._removeShadow(ctx); 21579 } 21580 shouldFill && ctx.fillText(_char, left, top); 21581 shouldStroke && ctx.strokeText(_char, left, top); 21582 21583 if (textDecoration || textDecoration !== '') { 21584 offset = this._fontSizeFraction * lineHeight / this.lineHeight; 21585 this._renderCharDecoration(ctx, textDecoration, left, top, offset, charWidth, charHeight); 21586 } 21587 21588 decl && ctx.restore(); 21589 ctx.translate(charWidth, 0); 21590 }, 21591 21592 /** 21593 * @private 21594 * @param {Object} prevStyle 21595 * @param {Object} thisStyle 21596 */ 21597 _hasStyleChanged: function(prevStyle, thisStyle) { 21598 return (prevStyle.fill !== thisStyle.fill || 21599 prevStyle.fontSize !== thisStyle.fontSize || 21600 prevStyle.textBackgroundColor !== thisStyle.textBackgroundColor || 21601 prevStyle.textDecoration !== thisStyle.textDecoration || 21602 prevStyle.fontFamily !== thisStyle.fontFamily || 21603 prevStyle.fontWeight !== thisStyle.fontWeight || 21604 prevStyle.fontStyle !== thisStyle.fontStyle || 21605 prevStyle.stroke !== thisStyle.stroke || 21606 prevStyle.strokeWidth !== thisStyle.strokeWidth 21607 ); 21608 }, 21609 21610 /** 21611 * @private 21612 * @param {CanvasRenderingContext2D} ctx Context to render on 21613 */ 21614 _renderCharDecoration: function(ctx, textDecoration, left, top, offset, charWidth, charHeight) { 21615 21616 if (!textDecoration) { 21617 return; 21618 } 21619 21620 var decorationWeight = charHeight / 15, 21621 positions = { 21622 underline: top + charHeight / 10, 21623 'line-through': top - charHeight * (this._fontSizeFraction + this._fontSizeMult - 1) + decorationWeight, 21624 overline: top - (this._fontSizeMult - this._fontSizeFraction) * charHeight 21625 }, 21626 decorations = ['underline', 'line-through', 'overline'], i, decoration; 21627 21628 for (i = 0; i < decorations.length; i++) { 21629 decoration = decorations[i]; 21630 if (textDecoration.indexOf(decoration) > -1) { 21631 ctx.fillRect(left, positions[decoration], charWidth , decorationWeight); 21632 } 21633 } 21634 }, 21635 21636 /** 21637 * @private 21638 * @param {String} method 21639 * @param {CanvasRenderingContext2D} ctx Context to render on 21640 * @param {String} line 21641 */ 21642 _renderTextLine: function(method, ctx, line, left, top, lineIndex) { 21643 // to "cancel" this.fontSize subtraction in fabric.Text#_renderTextLine 21644 // the adding 0.03 is just to align text with itext by overlap test 21645 if (!this.isEmptyStyles()) { 21646 top += this.fontSize * (this._fontSizeFraction + 0.03); 21647 } 21648 this.callSuper('_renderTextLine', method, ctx, line, left, top, lineIndex); 21649 }, 21650 21651 /** 21652 * @private 21653 * @param {CanvasRenderingContext2D} ctx Context to render on 21654 */ 21655 _renderTextDecoration: function(ctx) { 21656 if (this.isEmptyStyles()) { 21657 return this.callSuper('_renderTextDecoration', ctx); 21658 } 21659 }, 21660 21661 /** 21662 * @private 21663 * @param {CanvasRenderingContext2D} ctx Context to render on 21664 */ 21665 _renderTextLinesBackground: function(ctx) { 21666 this.callSuper('_renderTextLinesBackground', ctx); 21667 21668 var lineTopOffset = 0, heightOfLine, 21669 lineWidth, lineLeftOffset, 21670 leftOffset = this._getLeftOffset(), 21671 topOffset = this._getTopOffset(), 21672 line, _char, style; 21673 21674 for (var i = 0, len = this._textLines.length; i < len; i++) { 21675 heightOfLine = this._getHeightOfLine(ctx, i); 21676 line = this._textLines[i]; 21677 21678 if (line === '' || !this.styles || !this._getLineStyle(i)) { 21679 lineTopOffset += heightOfLine; 21680 continue; 21681 } 21682 21683 lineWidth = this._getLineWidth(ctx, i); 21684 lineLeftOffset = this._getLineLeftOffset(lineWidth); 21685 21686 for (var j = 0, jlen = line.length; j < jlen; j++) { 21687 style = this._getStyleDeclaration(i, j); 21688 if (!style || !style.textBackgroundColor) { 21689 continue; 21690 } 21691 _char = line[j]; 21692 21693 ctx.fillStyle = style.textBackgroundColor; 21694 21695 ctx.fillRect( 21696 leftOffset + lineLeftOffset + this._getWidthOfCharsAt(ctx, i, j), 21697 topOffset + lineTopOffset, 21698 this._getWidthOfChar(ctx, _char, i, j) + 1, 21699 heightOfLine / this.lineHeight 21700 ); 21701 } 21702 lineTopOffset += heightOfLine; 21703 } 21704 }, 21705 21706 /** 21707 * @private 21708 */ 21709 _getCacheProp: function(_char, styleDeclaration) { 21710 return _char + 21711 styleDeclaration.fontFamily + 21712 styleDeclaration.fontSize + 21713 styleDeclaration.fontWeight + 21714 styleDeclaration.fontStyle + 21715 styleDeclaration.shadow; 21716 }, 21717 21718 /** 21719 * @private 21720 * @param {CanvasRenderingContext2D} ctx Context to render on 21721 * @param {String} _char 21722 * @param {Number} lineIndex 21723 * @param {Number} charIndex 21724 * @param {Object} [decl] 21725 */ 21726 _applyCharStylesGetWidth: function(ctx, _char, lineIndex, charIndex, decl) { 21727 var charDecl = this._getStyleDeclaration(lineIndex, charIndex), 21728 styleDeclaration = (decl && clone(decl)) || clone(charDecl), width; 21729 21730 this._applyFontStyles(styleDeclaration); 21731 21732 var cacheProp = this._getCacheProp(_char, styleDeclaration); 21733 21734 // short-circuit if no styles for this char 21735 // global style from object is always applyed and handled by save and restore 21736 if (!charDecl && this._charWidthsCache[cacheProp] && this.caching) { 21737 return this._charWidthsCache[cacheProp]; 21738 } 21739 21740 if (typeof styleDeclaration.shadow === 'string') { 21741 styleDeclaration.shadow = new fabric.Shadow(styleDeclaration.shadow); 21742 } 21743 21744 var fill = styleDeclaration.fill || this.fill; 21745 ctx.fillStyle = fill.toLive 21746 ? fill.toLive(ctx, this) 21747 : fill; 21748 21749 if (styleDeclaration.stroke) { 21750 ctx.strokeStyle = (styleDeclaration.stroke && styleDeclaration.stroke.toLive) 21751 ? styleDeclaration.stroke.toLive(ctx, this) 21752 : styleDeclaration.stroke; 21753 } 21754 21755 ctx.lineWidth = styleDeclaration.strokeWidth || this.strokeWidth; 21756 ctx.font = this._getFontDeclaration.call(styleDeclaration); 21757 21758 //if we want this._setShadow.call to work with styleDeclarion 21759 //we have to add those references 21760 if (styleDeclaration.shadow) { 21761 styleDeclaration.scaleX = this.scaleX; 21762 styleDeclaration.scaleY = this.scaleY; 21763 styleDeclaration.canvas = this.canvas; 21764 this._setShadow.call(styleDeclaration, ctx); 21765 } 21766 21767 if (!this.caching || !this._charWidthsCache[cacheProp]) { 21768 width = ctx.measureText(_char).width; 21769 this.caching && (this._charWidthsCache[cacheProp] = width); 21770 } 21771 21772 return this._charWidthsCache[cacheProp]; 21773 }, 21774 21775 /** 21776 * @private 21777 * @param {Object} styleDeclaration 21778 */ 21779 _applyFontStyles: function(styleDeclaration) { 21780 if (!styleDeclaration.fontFamily) { 21781 styleDeclaration.fontFamily = this.fontFamily; 21782 } 21783 if (!styleDeclaration.fontSize) { 21784 styleDeclaration.fontSize = this.fontSize; 21785 } 21786 if (!styleDeclaration.fontWeight) { 21787 styleDeclaration.fontWeight = this.fontWeight; 21788 } 21789 if (!styleDeclaration.fontStyle) { 21790 styleDeclaration.fontStyle = this.fontStyle; 21791 } 21792 }, 21793 21794 /** 21795 * @param {Number} lineIndex 21796 * @param {Number} charIndex 21797 * @param {Boolean} [returnCloneOrEmpty=false] 21798 * @private 21799 */ 21800 _getStyleDeclaration: function(lineIndex, charIndex, returnCloneOrEmpty) { 21801 if (returnCloneOrEmpty) { 21802 return (this.styles[lineIndex] && this.styles[lineIndex][charIndex]) 21803 ? clone(this.styles[lineIndex][charIndex]) 21804 : { }; 21805 } 21806 21807 return this.styles[lineIndex] && this.styles[lineIndex][charIndex] ? this.styles[lineIndex][charIndex] : null; 21808 }, 21809 21810 /** 21811 * @param {Number} lineIndex 21812 * @param {Number} charIndex 21813 * @param {Object} style 21814 * @private 21815 */ 21816 _setStyleDeclaration: function(lineIndex, charIndex, style) { 21817 this.styles[lineIndex][charIndex] = style; 21818 }, 21819 21820 /** 21821 * 21822 * @param {Number} lineIndex 21823 * @param {Number} charIndex 21824 * @private 21825 */ 21826 _deleteStyleDeclaration: function(lineIndex, charIndex) { 21827 delete this.styles[lineIndex][charIndex]; 21828 }, 21829 21830 /** 21831 * @param {Number} lineIndex 21832 * @private 21833 */ 21834 _getLineStyle: function(lineIndex) { 21835 return this.styles[lineIndex]; 21836 }, 21837 21838 /** 21839 * @param {Number} lineIndex 21840 * @param {Object} style 21841 * @private 21842 */ 21843 _setLineStyle: function(lineIndex, style) { 21844 this.styles[lineIndex] = style; 21845 }, 21846 21847 /** 21848 * @param {Number} lineIndex 21849 * @private 21850 */ 21851 _deleteLineStyle: function(lineIndex) { 21852 delete this.styles[lineIndex]; 21853 }, 21854 21855 /** 21856 * @private 21857 * @param {CanvasRenderingContext2D} ctx Context to render on 21858 */ 21859 _getWidthOfChar: function(ctx, _char, lineIndex, charIndex) { 21860 if (this.textAlign === 'justify' && this._reSpacesAndTabs.test(_char)) { 21861 return this._getWidthOfSpace(ctx, lineIndex); 21862 } 21863 21864 var styleDeclaration = this._getStyleDeclaration(lineIndex, charIndex, true); 21865 this._applyFontStyles(styleDeclaration); 21866 var cacheProp = this._getCacheProp(_char, styleDeclaration); 21867 21868 if (this._charWidthsCache[cacheProp] && this.caching) { 21869 return this._charWidthsCache[cacheProp]; 21870 } 21871 else if (ctx) { 21872 ctx.save(); 21873 var width = this._applyCharStylesGetWidth(ctx, _char, lineIndex, charIndex); 21874 ctx.restore(); 21875 return width; 21876 } 21877 }, 21878 21879 /** 21880 * @private 21881 * @param {CanvasRenderingContext2D} ctx Context to render on 21882 */ 21883 _getHeightOfChar: function(ctx, lineIndex, charIndex) { 21884 var style = this._getStyleDeclaration(lineIndex, charIndex); 21885 return style && style.fontSize ? style.fontSize : this.fontSize; 21886 }, 21887 21888 /** 21889 * @private 21890 * @param {CanvasRenderingContext2D} ctx Context to render on 21891 */ 21892 _getWidthOfCharsAt: function(ctx, lineIndex, charIndex) { 21893 var width = 0, i, _char; 21894 for (i = 0; i < charIndex; i++) { 21895 _char = this._textLines[lineIndex][i]; 21896 width += this._getWidthOfChar(ctx, _char, lineIndex, i); 21897 } 21898 return width; 21899 }, 21900 21901 /** 21902 * @private 21903 * @param {CanvasRenderingContext2D} ctx Context to render on 21904 */ 21905 _getLineWidth: function(ctx, lineIndex) { 21906 if (this.__lineWidths[lineIndex]) { 21907 return this.__lineWidths[lineIndex]; 21908 } 21909 this.__lineWidths[lineIndex] = this._getWidthOfCharsAt(ctx, lineIndex, this._textLines[lineIndex].length); 21910 return this.__lineWidths[lineIndex]; 21911 }, 21912 21913 /** 21914 * @private 21915 * @param {CanvasRenderingContext2D} ctx Context to render on 21916 * @param {Number} lineIndex 21917 */ 21918 _getWidthOfSpace: function (ctx, lineIndex) { 21919 if (this.__widthOfSpace[lineIndex]) { 21920 return this.__widthOfSpace[lineIndex]; 21921 } 21922 var line = this._textLines[lineIndex], 21923 wordsWidth = this._getWidthOfWords(ctx, line, lineIndex, 0), 21924 widthDiff = this.width - wordsWidth, 21925 numSpaces = line.length - line.replace(this._reSpacesAndTabs, '').length, 21926 width = widthDiff / numSpaces; 21927 this.__widthOfSpace[lineIndex] = width; 21928 return width; 21929 }, 21930 21931 /** 21932 * @private 21933 * @param {CanvasRenderingContext2D} ctx Context to render on 21934 * @param {Number} line 21935 * @param {Number} lineIndex 21936 */ 21937 _getWidthOfWords: function (ctx, line, lineIndex, charOffset) { 21938 var width = 0; 21939 21940 for (var charIndex = 0; charIndex < line.length; charIndex++) { 21941 var _char = line[charIndex]; 21942 21943 if (!_char.match(/\s/)) { 21944 width += this._getWidthOfChar(ctx, _char, lineIndex, charIndex + charOffset); 21945 } 21946 } 21947 21948 return width; 21949 }, 21950 21951 /** 21952 * @private 21953 * @param {CanvasRenderingContext2D} ctx Context to render on 21954 */ 21955 _getHeightOfLine: function(ctx, lineIndex) { 21956 if (this.__lineHeights[lineIndex]) { 21957 return this.__lineHeights[lineIndex]; 21958 } 21959 21960 var line = this._textLines[lineIndex], 21961 maxHeight = this._getHeightOfChar(ctx, lineIndex, 0); 21962 21963 for (var i = 1, len = line.length; i < len; i++) { 21964 var currentCharHeight = this._getHeightOfChar(ctx, lineIndex, i); 21965 if (currentCharHeight > maxHeight) { 21966 maxHeight = currentCharHeight; 21967 } 21968 } 21969 this.__lineHeights[lineIndex] = maxHeight * this.lineHeight * this._fontSizeMult; 21970 return this.__lineHeights[lineIndex]; 21971 }, 21972 21973 /** 21974 * @private 21975 * @param {CanvasRenderingContext2D} ctx Context to render on 21976 */ 21977 _getTextHeight: function(ctx) { 21978 var height = 0; 21979 for (var i = 0, len = this._textLines.length; i < len; i++) { 21980 height += this._getHeightOfLine(ctx, i); 21981 } 21982 return height; 21983 }, 21984 21985 /** 21986 * Returns object representation of an instance 21987 * @method toObject 21988 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 21989 * @return {Object} object representation of an instance 21990 */ 21991 toObject: function(propertiesToInclude) { 21992 var clonedStyles = { }, i, j, row; 21993 for (i in this.styles) { 21994 row = this.styles[i]; 21995 clonedStyles[i] = { }; 21996 for (j in row) { 21997 clonedStyles[i][j] = clone(row[j]); 21998 } 21999 } 22000 return fabric.util.object.extend(this.callSuper('toObject', propertiesToInclude), { 22001 styles: clonedStyles 22002 }); 22003 } 22004 }); 22005 22006 /** 22007 * Returns fabric.IText instance from an object representation 22008 * @static 22009 * @memberOf fabric.IText 22010 * @param {Object} object Object to create an instance from 22011 * @return {fabric.IText} instance of fabric.IText 22012 */ 22013 fabric.IText.fromObject = function(object) { 22014 return new fabric.IText(object.text, clone(object)); 22015 }; 22016 })(); 22017 22018 22019 (function() { 22020 22021 var clone = fabric.util.object.clone; 22022 22023 fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { 22024 22025 /** 22026 * Initializes all the interactive behavior of IText 22027 */ 22028 initBehavior: function() { 22029 this.initAddedHandler(); 22030 this.initRemovedHandler(); 22031 this.initCursorSelectionHandlers(); 22032 this.initDoubleClickSimulation(); 22033 }, 22034 22035 /** 22036 * Initializes "selected" event handler 22037 */ 22038 initSelectedHandler: function() { 22039 this.on('selected', function() { 22040 22041 var _this = this; 22042 setTimeout(function() { 22043 _this.selected = true; 22044 }, 100); 22045 }); 22046 }, 22047 22048 /** 22049 * Initializes "added" event handler 22050 */ 22051 initAddedHandler: function() { 22052 var _this = this; 22053 this.on('added', function() { 22054 if (this.canvas && !this.canvas._hasITextHandlers) { 22055 this.canvas._hasITextHandlers = true; 22056 this._initCanvasHandlers(); 22057 } 22058 22059 // Track IText instances per-canvas. Only register in this array once added 22060 // to a canvas; we don't want to leak a reference to the instance forever 22061 // simply because it existed at some point. 22062 // (Might be added to a collection, but not on a canvas.) 22063 if (_this.canvas) { 22064 _this.canvas._iTextInstances = _this.canvas._iTextInstances || []; 22065 _this.canvas._iTextInstances.push(_this); 22066 } 22067 }); 22068 }, 22069 22070 initRemovedHandler: function() { 22071 var _this = this; 22072 this.on('removed', function() { 22073 // (Might be removed from a collection, but not on a canvas.) 22074 if (_this.canvas) { 22075 _this.canvas._iTextInstances = _this.canvas._iTextInstances || []; 22076 fabric.util.removeFromArray(_this.canvas._iTextInstances, _this); 22077 } 22078 }); 22079 }, 22080 22081 /** 22082 * @private 22083 */ 22084 _initCanvasHandlers: function() { 22085 var _this = this; 22086 22087 this.canvas.on('selection:cleared', function() { 22088 fabric.IText.prototype.exitEditingOnOthers(_this.canvas); 22089 }); 22090 22091 this.canvas.on('mouse:up', function() { 22092 if (_this.canvas._iTextInstances) { 22093 _this.canvas._iTextInstances.forEach(function(obj) { 22094 obj.__isMousedown = false; 22095 }); 22096 } 22097 }); 22098 22099 this.canvas.on('object:selected', function() { 22100 fabric.IText.prototype.exitEditingOnOthers(_this.canvas); 22101 }); 22102 }, 22103 22104 /** 22105 * @private 22106 */ 22107 _tick: function() { 22108 this._currentTickState = this._animateCursor(this, 1, this.cursorDuration, '_onTickComplete'); 22109 }, 22110 22111 /** 22112 * @private 22113 */ 22114 _animateCursor: function(obj, targetOpacity, duration, completeMethod) { 22115 22116 var tickState; 22117 22118 tickState = { 22119 isAborted: false, 22120 abort: function() { 22121 this.isAborted = true; 22122 }, 22123 }; 22124 22125 obj.animate('_currentCursorOpacity', targetOpacity, { 22126 duration: duration, 22127 onComplete: function() { 22128 if (!tickState.isAborted) { 22129 obj[completeMethod](); 22130 } 22131 }, 22132 onChange: function() { 22133 if (obj.canvas) { 22134 obj.canvas.clearContext(obj.canvas.contextTop || obj.ctx); 22135 obj.renderCursorOrSelection(); 22136 } 22137 }, 22138 abort: function() { 22139 return tickState.isAborted; 22140 } 22141 }); 22142 return tickState; 22143 }, 22144 22145 /** 22146 * @private 22147 */ 22148 _onTickComplete: function() { 22149 22150 var _this = this; 22151 22152 if (this._cursorTimeout1) { 22153 clearTimeout(this._cursorTimeout1); 22154 } 22155 this._cursorTimeout1 = setTimeout(function() { 22156 _this._currentTickCompleteState = _this._animateCursor(_this, 0, this.cursorDuration / 2, '_tick'); 22157 }, 100); 22158 }, 22159 22160 /** 22161 * Initializes delayed cursor 22162 */ 22163 initDelayedCursor: function(restart) { 22164 var _this = this, 22165 delay = restart ? 0 : this.cursorDelay; 22166 22167 this._currentTickState && this._currentTickState.abort(); 22168 this._currentTickCompleteState && this._currentTickCompleteState.abort(); 22169 clearTimeout(this._cursorTimeout1); 22170 this._currentCursorOpacity = 1; 22171 if (this.canvas) { 22172 this.canvas.clearContext(this.canvas.contextTop || this.ctx); 22173 this.renderCursorOrSelection(); 22174 } 22175 if (this._cursorTimeout2) { 22176 clearTimeout(this._cursorTimeout2); 22177 } 22178 this._cursorTimeout2 = setTimeout(function() { 22179 _this._tick(); 22180 }, delay); 22181 }, 22182 22183 /** 22184 * Aborts cursor animation and clears all timeouts 22185 */ 22186 abortCursorAnimation: function() { 22187 this._currentTickState && this._currentTickState.abort(); 22188 this._currentTickCompleteState && this._currentTickCompleteState.abort(); 22189 22190 clearTimeout(this._cursorTimeout1); 22191 clearTimeout(this._cursorTimeout2); 22192 22193 this._currentCursorOpacity = 0; 22194 this.canvas && this.canvas.clearContext(this.canvas.contextTop || this.ctx); 22195 }, 22196 22197 /** 22198 * Selects entire text 22199 */ 22200 selectAll: function() { 22201 this.setSelectionStart(0); 22202 this.setSelectionEnd(this.text.length); 22203 }, 22204 22205 /** 22206 * Returns selected text 22207 * @return {String} 22208 */ 22209 getSelectedText: function() { 22210 return this.text.slice(this.selectionStart, this.selectionEnd); 22211 }, 22212 22213 /** 22214 * Find new selection index representing start of current word according to current selection index 22215 * @param {Number} startFrom Surrent selection index 22216 * @return {Number} New selection index 22217 */ 22218 findWordBoundaryLeft: function(startFrom) { 22219 var offset = 0, index = startFrom - 1; 22220 22221 // remove space before cursor first 22222 if (this._reSpace.test(this.text.charAt(index))) { 22223 while (this._reSpace.test(this.text.charAt(index))) { 22224 offset++; 22225 index--; 22226 } 22227 } 22228 while (/\S/.test(this.text.charAt(index)) && index > -1) { 22229 offset++; 22230 index--; 22231 } 22232 22233 return startFrom - offset; 22234 }, 22235 22236 /** 22237 * Find new selection index representing end of current word according to current selection index 22238 * @param {Number} startFrom Current selection index 22239 * @return {Number} New selection index 22240 */ 22241 findWordBoundaryRight: function(startFrom) { 22242 var offset = 0, index = startFrom; 22243 22244 // remove space after cursor first 22245 if (this._reSpace.test(this.text.charAt(index))) { 22246 while (this._reSpace.test(this.text.charAt(index))) { 22247 offset++; 22248 index++; 22249 } 22250 } 22251 while (/\S/.test(this.text.charAt(index)) && index < this.text.length) { 22252 offset++; 22253 index++; 22254 } 22255 22256 return startFrom + offset; 22257 }, 22258 22259 /** 22260 * Find new selection index representing start of current line according to current selection index 22261 * @param {Number} startFrom Current selection index 22262 * @return {Number} New selection index 22263 */ 22264 findLineBoundaryLeft: function(startFrom) { 22265 var offset = 0, index = startFrom - 1; 22266 22267 while (!/\n/.test(this.text.charAt(index)) && index > -1) { 22268 offset++; 22269 index--; 22270 } 22271 22272 return startFrom - offset; 22273 }, 22274 22275 /** 22276 * Find new selection index representing end of current line according to current selection index 22277 * @param {Number} startFrom Current selection index 22278 * @return {Number} New selection index 22279 */ 22280 findLineBoundaryRight: function(startFrom) { 22281 var offset = 0, index = startFrom; 22282 22283 while (!/\n/.test(this.text.charAt(index)) && index < this.text.length) { 22284 offset++; 22285 index++; 22286 } 22287 22288 return startFrom + offset; 22289 }, 22290 22291 /** 22292 * Returns number of newlines in selected text 22293 * @return {Number} Number of newlines in selected text 22294 */ 22295 getNumNewLinesInSelectedText: function() { 22296 var selectedText = this.getSelectedText(), 22297 numNewLines = 0; 22298 22299 for (var i = 0, len = selectedText.length; i < len; i++) { 22300 if (selectedText[i] === '\n') { 22301 numNewLines++; 22302 } 22303 } 22304 return numNewLines; 22305 }, 22306 22307 /** 22308 * Finds index corresponding to beginning or end of a word 22309 * @param {Number} selectionStart Index of a character 22310 * @param {Number} direction 1 or -1 22311 * @return {Number} Index of the beginning or end of a word 22312 */ 22313 searchWordBoundary: function(selectionStart, direction) { 22314 var index = this._reSpace.test(this.text.charAt(selectionStart)) ? selectionStart - 1 : selectionStart, 22315 _char = this.text.charAt(index), 22316 reNonWord = /[ \n\.,;!\?\-]/; 22317 22318 while (!reNonWord.test(_char) && index > 0 && index < this.text.length) { 22319 index += direction; 22320 _char = this.text.charAt(index); 22321 } 22322 if (reNonWord.test(_char) && _char !== '\n') { 22323 index += direction === 1 ? 0 : 1; 22324 } 22325 return index; 22326 }, 22327 22328 /** 22329 * Selects a word based on the index 22330 * @param {Number} selectionStart Index of a character 22331 */ 22332 selectWord: function(selectionStart) { 22333 var newSelectionStart = this.searchWordBoundary(selectionStart, -1), /* search backwards */ 22334 newSelectionEnd = this.searchWordBoundary(selectionStart, 1); 22335 /* search forward */ 22336 22337 this.setSelectionStart(newSelectionStart); 22338 this.setSelectionEnd(newSelectionEnd); 22339 }, 22340 22341 /** 22342 * Selects a line based on the index 22343 * @param {Number} selectionStart Index of a character 22344 */ 22345 selectLine: function(selectionStart) { 22346 var newSelectionStart = this.findLineBoundaryLeft(selectionStart), 22347 newSelectionEnd = this.findLineBoundaryRight(selectionStart); 22348 22349 this.setSelectionStart(newSelectionStart); 22350 this.setSelectionEnd(newSelectionEnd); 22351 }, 22352 22353 /** 22354 * Enters editing state 22355 * @return {fabric.IText} thisArg 22356 * @chainable 22357 */ 22358 enterEditing: function(e) { 22359 if (this.isEditing || !this.editable) { 22360 return; 22361 } 22362 22363 if (this.canvas) { 22364 this.exitEditingOnOthers(this.canvas); 22365 } 22366 22367 this.isEditing = true; 22368 22369 this.initHiddenTextarea(e); 22370 this.hiddenTextarea.focus(); 22371 this._updateTextarea(); 22372 this._saveEditingProps(); 22373 this._setEditingProps(); 22374 22375 this._tick(); 22376 this.fire('editing:entered'); 22377 22378 if (!this.canvas) { 22379 return this; 22380 } 22381 22382 this.canvas.renderAll(); 22383 this.canvas.fire('text:editing:entered', { target: this }); 22384 this.initMouseMoveHandler(); 22385 return this; 22386 }, 22387 22388 exitEditingOnOthers: function(canvas) { 22389 if (canvas._iTextInstances) { 22390 canvas._iTextInstances.forEach(function(obj) { 22391 obj.selected = false; 22392 if (obj.isEditing) { 22393 obj.exitEditing(); 22394 } 22395 }); 22396 } 22397 }, 22398 22399 /** 22400 * Initializes "mousemove" event handler 22401 */ 22402 initMouseMoveHandler: function() { 22403 var _this = this; 22404 this.canvas.on('mouse:move', function(options) { 22405 if (!_this.__isMousedown || !_this.isEditing) { 22406 return; 22407 } 22408 22409 var newSelectionStart = _this.getSelectionStartFromPointer(options.e); 22410 if (newSelectionStart >= _this.__selectionStartOnMouseDown) { 22411 _this.setSelectionStart(_this.__selectionStartOnMouseDown); 22412 _this.setSelectionEnd(newSelectionStart); 22413 } 22414 else { 22415 _this.setSelectionStart(newSelectionStart); 22416 _this.setSelectionEnd(_this.__selectionStartOnMouseDown); 22417 } 22418 }); 22419 }, 22420 22421 /** 22422 * @private 22423 */ 22424 _setEditingProps: function() { 22425 this.hoverCursor = 'text'; 22426 22427 if (this.canvas) { 22428 this.canvas.defaultCursor = this.canvas.moveCursor = 'text'; 22429 } 22430 22431 this.borderColor = this.editingBorderColor; 22432 22433 this.hasControls = this.selectable = false; 22434 this.lockMovementX = this.lockMovementY = true; 22435 }, 22436 22437 /** 22438 * @private 22439 */ 22440 _updateTextarea: function() { 22441 if (!this.hiddenTextarea || this.inCompositionMode) { 22442 return; 22443 } 22444 22445 this.hiddenTextarea.value = this.text; 22446 this.hiddenTextarea.selectionStart = this.selectionStart; 22447 this.hiddenTextarea.selectionEnd = this.selectionEnd; 22448 if (this.selectionStart === this.selectionEnd) { 22449 var p = this._calcTextareaPosition(); 22450 this.hiddenTextarea.style.left = p.x + 'px'; 22451 this.hiddenTextarea.style.top = p.y + 'px'; 22452 } 22453 }, 22454 22455 /** 22456 * @private 22457 */ 22458 _calcTextareaPosition: function() { 22459 var chars = this.text.split(''), 22460 boundaries = this._getCursorBoundaries(chars, 'cursor'), 22461 cursorLocation = this.get2DCursorLocation(), 22462 lineIndex = cursorLocation.lineIndex, 22463 charIndex = cursorLocation.charIndex, 22464 charHeight = this.getCurrentCharFontSize(lineIndex, charIndex), 22465 leftOffset = (lineIndex === 0 && charIndex === 0) 22466 ? this._getLineLeftOffset(this._getLineWidth(this.ctx, lineIndex)) 22467 : boundaries.leftOffset, 22468 m = this.calcTransformMatrix(), 22469 p = { x: boundaries.left + leftOffset, y: boundaries.top + boundaries.topOffset + charHeight }; 22470 this.hiddenTextarea.style.fontSize = charHeight + 'px'; 22471 return fabric.util.transformPoint(p, m); 22472 }, 22473 22474 /** 22475 * @private 22476 */ 22477 _saveEditingProps: function() { 22478 this._savedProps = { 22479 hasControls: this.hasControls, 22480 borderColor: this.borderColor, 22481 lockMovementX: this.lockMovementX, 22482 lockMovementY: this.lockMovementY, 22483 hoverCursor: this.hoverCursor, 22484 defaultCursor: this.canvas && this.canvas.defaultCursor, 22485 moveCursor: this.canvas && this.canvas.moveCursor 22486 }; 22487 }, 22488 22489 /** 22490 * @private 22491 */ 22492 _restoreEditingProps: function() { 22493 if (!this._savedProps) { 22494 return; 22495 } 22496 22497 this.hoverCursor = this._savedProps.overCursor; 22498 this.hasControls = this._savedProps.hasControls; 22499 this.borderColor = this._savedProps.borderColor; 22500 this.lockMovementX = this._savedProps.lockMovementX; 22501 this.lockMovementY = this._savedProps.lockMovementY; 22502 22503 if (this.canvas) { 22504 this.canvas.defaultCursor = this._savedProps.defaultCursor; 22505 this.canvas.moveCursor = this._savedProps.moveCursor; 22506 } 22507 }, 22508 22509 /** 22510 * Exits from editing state 22511 * @return {fabric.IText} thisArg 22512 * @chainable 22513 */ 22514 exitEditing: function() { 22515 22516 this.selected = false; 22517 this.isEditing = false; 22518 this.selectable = true; 22519 22520 this.selectionEnd = this.selectionStart; 22521 this.hiddenTextarea && this.canvas && this.hiddenTextarea.parentNode.removeChild(this.hiddenTextarea); 22522 this.hiddenTextarea = null; 22523 22524 this.abortCursorAnimation(); 22525 this._restoreEditingProps(); 22526 this._currentCursorOpacity = 0; 22527 22528 this.fire('editing:exited'); 22529 this.canvas && this.canvas.fire('text:editing:exited', { target: this }); 22530 22531 return this; 22532 }, 22533 22534 /** 22535 * @private 22536 */ 22537 _removeExtraneousStyles: function() { 22538 for (var prop in this.styles) { 22539 if (!this._textLines[prop]) { 22540 delete this.styles[prop]; 22541 } 22542 } 22543 }, 22544 22545 /** 22546 * @private 22547 */ 22548 _removeCharsFromTo: function(start, end) { 22549 while (end !== start) { 22550 this._removeSingleCharAndStyle(start + 1); 22551 end--; 22552 } 22553 this.setSelectionStart(start); 22554 }, 22555 22556 _removeSingleCharAndStyle: function(index) { 22557 var isBeginningOfLine = this.text[index - 1] === '\n', 22558 indexStyle = isBeginningOfLine ? index : index - 1; 22559 this.removeStyleObject(isBeginningOfLine, indexStyle); 22560 this.text = this.text.slice(0, index - 1) + 22561 this.text.slice(index); 22562 22563 this._textLines = this._splitTextIntoLines(); 22564 }, 22565 22566 /** 22567 * Inserts characters where cursor is (replacing selection if one exists) 22568 * @param {String} _chars Characters to insert 22569 * @param {Boolean} useCopiedStyle use fabric.copiedTextStyle 22570 */ 22571 insertChars: function(_chars, useCopiedStyle) { 22572 var style; 22573 22574 if (this.selectionEnd - this.selectionStart > 1) { 22575 this._removeCharsFromTo(this.selectionStart, this.selectionEnd); 22576 this.setSelectionEnd(this.selectionStart); 22577 } 22578 //short circuit for block paste 22579 if (!useCopiedStyle && this.isEmptyStyles()) { 22580 this.insertChar(_chars, false); 22581 return; 22582 } 22583 for (var i = 0, len = _chars.length; i < len; i++) { 22584 if (useCopiedStyle) { 22585 style = fabric.copiedTextStyle[i]; 22586 } 22587 this.insertChar(_chars[i], i < len - 1, style); 22588 } 22589 }, 22590 22591 /** 22592 * Inserts a character where cursor is 22593 * @param {String} _char Characters to insert 22594 * @param {Boolean} skipUpdate trigger rendering and updates at the end of text insert 22595 * @param {Object} styleObject Style to be inserted for the new char 22596 */ 22597 insertChar: function(_char, skipUpdate, styleObject) { 22598 var isEndOfLine = this.text[this.selectionStart] === '\n'; 22599 this.text = this.text.slice(0, this.selectionStart) + 22600 _char + this.text.slice(this.selectionEnd); 22601 this._textLines = this._splitTextIntoLines(); 22602 this.insertStyleObjects(_char, isEndOfLine, styleObject); 22603 this.selectionStart += _char.length; 22604 this.selectionEnd = this.selectionStart; 22605 if (skipUpdate) { 22606 return; 22607 } 22608 this._updateTextarea(); 22609 this.canvas && this.canvas.renderAll(); 22610 this.setCoords(); 22611 this.fire('changed'); 22612 this.canvas && this.canvas.fire('text:changed', { target: this }); 22613 }, 22614 22615 /** 22616 * Inserts new style object 22617 * @param {Number} lineIndex Index of a line 22618 * @param {Number} charIndex Index of a char 22619 * @param {Boolean} isEndOfLine True if it's end of line 22620 */ 22621 insertNewlineStyleObject: function(lineIndex, charIndex, isEndOfLine) { 22622 22623 this.shiftLineStyles(lineIndex, +1); 22624 22625 if (!this.styles[lineIndex + 1]) { 22626 this.styles[lineIndex + 1] = {}; 22627 } 22628 22629 var currentCharStyle = {}, 22630 newLineStyles = {}; 22631 22632 if (this.styles[lineIndex] && this.styles[lineIndex][charIndex - 1]) { 22633 currentCharStyle = this.styles[lineIndex][charIndex - 1]; 22634 } 22635 22636 // if there's nothing after cursor, 22637 // we clone current char style onto the next (otherwise empty) line 22638 if (isEndOfLine) { 22639 newLineStyles[0] = clone(currentCharStyle); 22640 this.styles[lineIndex + 1] = newLineStyles; 22641 } 22642 // otherwise we clone styles of all chars 22643 // after cursor onto the next line, from the beginning 22644 else { 22645 for (var index in this.styles[lineIndex]) { 22646 if (parseInt(index, 10) >= charIndex) { 22647 newLineStyles[parseInt(index, 10) - charIndex] = this.styles[lineIndex][index]; 22648 // remove lines from the previous line since they're on a new line now 22649 delete this.styles[lineIndex][index]; 22650 } 22651 } 22652 this.styles[lineIndex + 1] = newLineStyles; 22653 } 22654 this._forceClearCache = true; 22655 }, 22656 22657 /** 22658 * Inserts style object for a given line/char index 22659 * @param {Number} lineIndex Index of a line 22660 * @param {Number} charIndex Index of a char 22661 * @param {Object} [style] Style object to insert, if given 22662 */ 22663 insertCharStyleObject: function(lineIndex, charIndex, style) { 22664 22665 var currentLineStyles = this.styles[lineIndex], 22666 currentLineStylesCloned = clone(currentLineStyles); 22667 22668 if (charIndex === 0 && !style) { 22669 charIndex = 1; 22670 } 22671 22672 // shift all char styles by 1 forward 22673 // 0,1,2,3 -> (charIndex=2) -> 0,1,3,4 -> (insert 2) -> 0,1,2,3,4 22674 for (var index in currentLineStylesCloned) { 22675 var numericIndex = parseInt(index, 10); 22676 22677 if (numericIndex >= charIndex) { 22678 currentLineStyles[numericIndex + 1] = currentLineStylesCloned[numericIndex]; 22679 22680 // only delete the style if there was nothing moved there 22681 if (!currentLineStylesCloned[numericIndex - 1]) { 22682 delete currentLineStyles[numericIndex]; 22683 } 22684 } 22685 } 22686 22687 this.styles[lineIndex][charIndex] = 22688 style || clone(currentLineStyles[charIndex - 1]); 22689 this._forceClearCache = true; 22690 }, 22691 22692 /** 22693 * Inserts style object(s) 22694 * @param {String} _chars Characters at the location where style is inserted 22695 * @param {Boolean} isEndOfLine True if it's end of line 22696 * @param {Object} [styleObject] Style to insert 22697 */ 22698 insertStyleObjects: function(_chars, isEndOfLine, styleObject) { 22699 // removed shortcircuit over isEmptyStyles 22700 22701 var cursorLocation = this.get2DCursorLocation(), 22702 lineIndex = cursorLocation.lineIndex, 22703 charIndex = cursorLocation.charIndex; 22704 22705 if (!this._getLineStyle(lineIndex)) { 22706 this._setLineStyle(lineIndex, {}); 22707 } 22708 22709 if (_chars === '\n') { 22710 this.insertNewlineStyleObject(lineIndex, charIndex, isEndOfLine); 22711 } 22712 else { 22713 this.insertCharStyleObject(lineIndex, charIndex, styleObject); 22714 } 22715 }, 22716 22717 /** 22718 * Shifts line styles up or down 22719 * @param {Number} lineIndex Index of a line 22720 * @param {Number} offset Can be -1 or +1 22721 */ 22722 shiftLineStyles: function(lineIndex, offset) { 22723 // shift all line styles by 1 upward 22724 var clonedStyles = clone(this.styles); 22725 for (var line in this.styles) { 22726 var numericLine = parseInt(line, 10); 22727 if (numericLine > lineIndex) { 22728 this.styles[numericLine + offset] = clonedStyles[numericLine]; 22729 if (!clonedStyles[numericLine - offset]) { 22730 delete this.styles[numericLine]; 22731 } 22732 } 22733 } 22734 //TODO: evaluate if delete old style lines with offset -1 22735 }, 22736 22737 /** 22738 * Removes style object 22739 * @param {Boolean} isBeginningOfLine True if cursor is at the beginning of line 22740 * @param {Number} [index] Optional index. When not given, current selectionStart is used. 22741 */ 22742 removeStyleObject: function(isBeginningOfLine, index) { 22743 22744 var cursorLocation = this.get2DCursorLocation(index), 22745 lineIndex = cursorLocation.lineIndex, 22746 charIndex = cursorLocation.charIndex; 22747 22748 this._removeStyleObject(isBeginningOfLine, cursorLocation, lineIndex, charIndex); 22749 }, 22750 22751 _getTextOnPreviousLine: function(lIndex) { 22752 return this._textLines[lIndex - 1]; 22753 }, 22754 22755 _removeStyleObject: function(isBeginningOfLine, cursorLocation, lineIndex, charIndex) { 22756 22757 if (isBeginningOfLine) { 22758 var textOnPreviousLine = this._getTextOnPreviousLine(cursorLocation.lineIndex), 22759 newCharIndexOnPrevLine = textOnPreviousLine ? textOnPreviousLine.length : 0; 22760 22761 if (!this.styles[lineIndex - 1]) { 22762 this.styles[lineIndex - 1] = {}; 22763 } 22764 for (charIndex in this.styles[lineIndex]) { 22765 this.styles[lineIndex - 1][parseInt(charIndex, 10) + newCharIndexOnPrevLine] 22766 = this.styles[lineIndex][charIndex]; 22767 } 22768 this.shiftLineStyles(cursorLocation.lineIndex, -1); 22769 } 22770 else { 22771 var currentLineStyles = this.styles[lineIndex]; 22772 22773 if (currentLineStyles) { 22774 delete currentLineStyles[charIndex]; 22775 } 22776 var currentLineStylesCloned = clone(currentLineStyles); 22777 // shift all styles by 1 backwards 22778 for (var i in currentLineStylesCloned) { 22779 var numericIndex = parseInt(i, 10); 22780 if (numericIndex >= charIndex && numericIndex !== 0) { 22781 currentLineStyles[numericIndex - 1] = currentLineStylesCloned[numericIndex]; 22782 delete currentLineStyles[numericIndex]; 22783 } 22784 } 22785 } 22786 }, 22787 22788 /** 22789 * Inserts new line 22790 */ 22791 insertNewline: function() { 22792 this.insertChars('\n'); 22793 } 22794 }); 22795 })(); 22796 22797 22798 fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { 22799 /** 22800 * Initializes "dbclick" event handler 22801 */ 22802 initDoubleClickSimulation: function() { 22803 22804 // for double click 22805 this.__lastClickTime = +new Date(); 22806 22807 // for triple click 22808 this.__lastLastClickTime = +new Date(); 22809 22810 this.__lastPointer = { }; 22811 22812 this.on('mousedown', this.onMouseDown.bind(this)); 22813 }, 22814 22815 onMouseDown: function(options) { 22816 22817 this.__newClickTime = +new Date(); 22818 var newPointer = this.canvas.getPointer(options.e); 22819 22820 if (this.isTripleClick(newPointer)) { 22821 this.fire('tripleclick', options); 22822 this._stopEvent(options.e); 22823 } 22824 else if (this.isDoubleClick(newPointer)) { 22825 this.fire('dblclick', options); 22826 this._stopEvent(options.e); 22827 } 22828 22829 this.__lastLastClickTime = this.__lastClickTime; 22830 this.__lastClickTime = this.__newClickTime; 22831 this.__lastPointer = newPointer; 22832 this.__lastIsEditing = this.isEditing; 22833 this.__lastSelected = this.selected; 22834 }, 22835 22836 isDoubleClick: function(newPointer) { 22837 return this.__newClickTime - this.__lastClickTime < 500 && 22838 this.__lastPointer.x === newPointer.x && 22839 this.__lastPointer.y === newPointer.y && this.__lastIsEditing; 22840 }, 22841 22842 isTripleClick: function(newPointer) { 22843 return this.__newClickTime - this.__lastClickTime < 500 && 22844 this.__lastClickTime - this.__lastLastClickTime < 500 && 22845 this.__lastPointer.x === newPointer.x && 22846 this.__lastPointer.y === newPointer.y; 22847 }, 22848 22849 /** 22850 * @private 22851 */ 22852 _stopEvent: function(e) { 22853 e.preventDefault && e.preventDefault(); 22854 e.stopPropagation && e.stopPropagation(); 22855 }, 22856 22857 /** 22858 * Initializes event handlers related to cursor or selection 22859 */ 22860 initCursorSelectionHandlers: function() { 22861 this.initSelectedHandler(); 22862 this.initMousedownHandler(); 22863 this.initMouseupHandler(); 22864 this.initClicks(); 22865 }, 22866 22867 /** 22868 * Initializes double and triple click event handlers 22869 */ 22870 initClicks: function() { 22871 this.on('dblclick', function(options) { 22872 this.selectWord(this.getSelectionStartFromPointer(options.e)); 22873 }); 22874 this.on('tripleclick', function(options) { 22875 this.selectLine(this.getSelectionStartFromPointer(options.e)); 22876 }); 22877 }, 22878 22879 /** 22880 * Initializes "mousedown" event handler 22881 */ 22882 initMousedownHandler: function() { 22883 this.on('mousedown', function(options) { 22884 if (!this.editable) { 22885 return; 22886 } 22887 var pointer = this.canvas.getPointer(options.e); 22888 22889 this.__mousedownX = pointer.x; 22890 this.__mousedownY = pointer.y; 22891 this.__isMousedown = true; 22892 22893 if (this.hiddenTextarea && this.canvas) { 22894 this.canvas.wrapperEl.appendChild(this.hiddenTextarea); 22895 } 22896 22897 if (this.selected) { 22898 this.setCursorByClick(options.e); 22899 } 22900 22901 if (this.isEditing) { 22902 this.__selectionStartOnMouseDown = this.selectionStart; 22903 this.initDelayedCursor(true); 22904 } 22905 }); 22906 }, 22907 22908 /** 22909 * @private 22910 */ 22911 _isObjectMoved: function(e) { 22912 var pointer = this.canvas.getPointer(e); 22913 22914 return this.__mousedownX !== pointer.x || 22915 this.__mousedownY !== pointer.y; 22916 }, 22917 22918 /** 22919 * Initializes "mouseup" event handler 22920 */ 22921 initMouseupHandler: function() { 22922 this.on('mouseup', function(options) { 22923 this.__isMousedown = false; 22924 if (!this.editable || this._isObjectMoved(options.e)) { 22925 return; 22926 } 22927 22928 if (this.__lastSelected && !this.__corner) { 22929 this.enterEditing(options.e); 22930 this.initDelayedCursor(true); 22931 } 22932 this.selected = true; 22933 }); 22934 }, 22935 22936 /** 22937 * Changes cursor location in a text depending on passed pointer (x/y) object 22938 * @param {Event} e Event object 22939 */ 22940 setCursorByClick: function(e) { 22941 var newSelectionStart = this.getSelectionStartFromPointer(e); 22942 22943 if (e.shiftKey) { 22944 if (newSelectionStart < this.selectionStart) { 22945 this.setSelectionEnd(this.selectionStart); 22946 this.setSelectionStart(newSelectionStart); 22947 } 22948 else { 22949 this.setSelectionEnd(newSelectionStart); 22950 } 22951 } 22952 else { 22953 this.setSelectionStart(newSelectionStart); 22954 this.setSelectionEnd(newSelectionStart); 22955 } 22956 }, 22957 22958 /** 22959 * Returns index of a character corresponding to where an object was clicked 22960 * @param {Event} e Event object 22961 * @return {Number} Index of a character 22962 */ 22963 getSelectionStartFromPointer: function(e) { 22964 var mouseOffset = this.getLocalPointer(e), 22965 prevWidth = 0, 22966 width = 0, 22967 height = 0, 22968 charIndex = 0, 22969 newSelectionStart, 22970 line; 22971 22972 for (var i = 0, len = this._textLines.length; i < len; i++) { 22973 line = this._textLines[i]; 22974 height += this._getHeightOfLine(this.ctx, i) * this.scaleY; 22975 22976 var widthOfLine = this._getLineWidth(this.ctx, i), 22977 lineLeftOffset = this._getLineLeftOffset(widthOfLine); 22978 22979 width = lineLeftOffset * this.scaleX; 22980 22981 for (var j = 0, jlen = line.length; j < jlen; j++) { 22982 22983 prevWidth = width; 22984 22985 width += this._getWidthOfChar(this.ctx, line[j], i, this.flipX ? jlen - j : j) * 22986 this.scaleX; 22987 22988 if (height <= mouseOffset.y || width <= mouseOffset.x) { 22989 charIndex++; 22990 continue; 22991 } 22992 22993 return this._getNewSelectionStartFromOffset( 22994 mouseOffset, prevWidth, width, charIndex + i, jlen); 22995 } 22996 22997 if (mouseOffset.y < height) { 22998 //this happens just on end of lines. 22999 return this._getNewSelectionStartFromOffset( 23000 mouseOffset, prevWidth, width, charIndex + i - 1, jlen); 23001 } 23002 } 23003 23004 // clicked somewhere after all chars, so set at the end 23005 if (typeof newSelectionStart === 'undefined') { 23006 return this.text.length; 23007 } 23008 }, 23009 23010 /** 23011 * @private 23012 */ 23013 _getNewSelectionStartFromOffset: function(mouseOffset, prevWidth, width, index, jlen) { 23014 23015 var distanceBtwLastCharAndCursor = mouseOffset.x - prevWidth, 23016 distanceBtwNextCharAndCursor = width - mouseOffset.x, 23017 offset = distanceBtwNextCharAndCursor > distanceBtwLastCharAndCursor ? 0 : 1, 23018 newSelectionStart = index + offset; 23019 23020 // if object is horizontally flipped, mirror cursor location from the end 23021 if (this.flipX) { 23022 newSelectionStart = jlen - newSelectionStart; 23023 } 23024 23025 if (newSelectionStart > this.text.length) { 23026 newSelectionStart = this.text.length; 23027 } 23028 23029 return newSelectionStart; 23030 } 23031 }); 23032 23033 23034 fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { 23035 23036 /** 23037 * Initializes hidden textarea (needed to bring up keyboard in iOS) 23038 */ 23039 initHiddenTextarea: function(e) { 23040 var p; 23041 if (e && this.canvas) { 23042 p = this.canvas.getPointer(e); 23043 } 23044 else { 23045 this.oCoords || this.setCoords(); 23046 p = this.oCoords.tl; 23047 } 23048 this.hiddenTextarea = fabric.document.createElement('textarea'); 23049 23050 this.hiddenTextarea.setAttribute('autocapitalize', 'off'); 23051 this.hiddenTextarea.style.cssText = 'position: absolute; top: ' + p.y + 'px; left: ' + p.x + 'px; opacity: 0;' 23052 + ' width: 0px; height: 0px; z-index: -999;'; 23053 if (this.canvas) { 23054 this.canvas.lowerCanvasEl.parentNode.appendChild(this.hiddenTextarea); 23055 } 23056 else { 23057 fabric.document.body.appendChild(this.hiddenTextarea); 23058 } 23059 23060 fabric.util.addListener(this.hiddenTextarea, 'keydown', this.onKeyDown.bind(this)); 23061 fabric.util.addListener(this.hiddenTextarea, 'keyup', this.onKeyUp.bind(this)); 23062 fabric.util.addListener(this.hiddenTextarea, 'input', this.onInput.bind(this)); 23063 fabric.util.addListener(this.hiddenTextarea, 'copy', this.copy.bind(this)); 23064 fabric.util.addListener(this.hiddenTextarea, 'cut', this.cut.bind(this)); 23065 fabric.util.addListener(this.hiddenTextarea, 'paste', this.paste.bind(this)); 23066 fabric.util.addListener(this.hiddenTextarea, 'compositionstart', this.onCompositionStart.bind(this)); 23067 fabric.util.addListener(this.hiddenTextarea, 'compositionupdate', this.onCompositionUpdate.bind(this)); 23068 fabric.util.addListener(this.hiddenTextarea, 'compositionend', this.onCompositionEnd.bind(this)); 23069 23070 if (!this._clickHandlerInitialized && this.canvas) { 23071 fabric.util.addListener(this.canvas.upperCanvasEl, 'click', this.onClick.bind(this)); 23072 this._clickHandlerInitialized = true; 23073 } 23074 }, 23075 23076 /** 23077 * @private 23078 */ 23079 _keysMap: { 23080 8: 'removeChars', 23081 9: 'exitEditing', 23082 27: 'exitEditing', 23083 13: 'insertNewline', 23084 33: 'moveCursorUp', 23085 34: 'moveCursorDown', 23086 35: 'moveCursorRight', 23087 36: 'moveCursorLeft', 23088 37: 'moveCursorLeft', 23089 38: 'moveCursorUp', 23090 39: 'moveCursorRight', 23091 40: 'moveCursorDown', 23092 46: 'forwardDelete' 23093 }, 23094 23095 /** 23096 * @private 23097 */ 23098 _ctrlKeysMap: { 23099 65: 'selectAll', 23100 67: 'copy', 23101 88: 'cut' 23102 }, 23103 23104 onClick: function() { 23105 // No need to trigger click event here, focus is enough to have the keyboard appear on Android 23106 this.hiddenTextarea && this.hiddenTextarea.focus(); 23107 }, 23108 23109 /** 23110 * Handles keyup event 23111 * @param {Event} e Event object 23112 */ 23113 onKeyDown: function(e) { 23114 if (!this.isEditing) { 23115 return; 23116 } 23117 if (e.keyCode in this._keysMap) { 23118 this[this._keysMap[e.keyCode]](e); 23119 } 23120 else { 23121 return; 23122 } 23123 e.stopImmediatePropagation(); 23124 e.preventDefault(); 23125 this.canvas && this.canvas.renderAll(); 23126 }, 23127 23128 /** 23129 * Handles keyup event 23130 * if a copy/cut event fired, keyup is dismissed 23131 * @param {Event} e Event object 23132 */ 23133 onKeyUp: function(e) { 23134 if (!this.isEditing || this._copyDone) { 23135 this._copyDone = false; 23136 return; 23137 } 23138 if ((e.keyCode in this._ctrlKeysMap) && (e.ctrlKey || e.metaKey)) { 23139 this[this._ctrlKeysMap[e.keyCode]](e); 23140 } 23141 else { 23142 return; 23143 } 23144 e.stopImmediatePropagation(); 23145 e.preventDefault(); 23146 this.canvas && this.canvas.renderAll(); 23147 }, 23148 23149 /** 23150 * Handles onInput event 23151 * @param {Event} e Event object 23152 */ 23153 onInput: function(e) { 23154 if (!this.isEditing || this.inCompositionMode) { 23155 return; 23156 } 23157 var offset = this.selectionStart || 0, 23158 offsetEnd = this.selectionEnd || 0, 23159 textLength = this.text.length, 23160 newTextLength = this.hiddenTextarea.value.length, 23161 diff, charsToInsert, start; 23162 if (newTextLength > textLength) { 23163 //we added some character 23164 start = this._selectionDirection === 'left' ? offsetEnd : offset; 23165 diff = newTextLength - textLength; 23166 charsToInsert = this.hiddenTextarea.value.slice(start, start + diff); 23167 } 23168 else { 23169 //we selected a portion of text and then input something else. 23170 //Internet explorer does not trigger this else 23171 diff = newTextLength - textLength + offsetEnd - offset; 23172 charsToInsert = this.hiddenTextarea.value.slice(offset, offset + diff); 23173 } 23174 this.insertChars(charsToInsert); 23175 e.stopPropagation(); 23176 }, 23177 23178 /** 23179 * Composition start 23180 */ 23181 onCompositionStart: function() { 23182 this.inCompositionMode = true; 23183 this.prevCompositionLength = 0; 23184 this.compositionStart = this.selectionStart; 23185 }, 23186 23187 /** 23188 * Composition end 23189 */ 23190 onCompositionEnd: function() { 23191 this.inCompositionMode = false; 23192 }, 23193 23194 /** 23195 * Composition update 23196 */ 23197 onCompositionUpdate: function(e) { 23198 var data = e.data; 23199 this.selectionStart = this.compositionStart; 23200 this.selectionEnd = this.selectionEnd === this.selectionStart ? 23201 this.compositionStart + this.prevCompositionLength : this.selectionEnd; 23202 this.insertChars(data, false); 23203 this.prevCompositionLength = data.length; 23204 }, 23205 23206 /** 23207 * Forward delete 23208 */ 23209 forwardDelete: function(e) { 23210 if (this.selectionStart === this.selectionEnd) { 23211 if (this.selectionStart === this.text.length) { 23212 return; 23213 } 23214 this.moveCursorRight(e); 23215 } 23216 this.removeChars(e); 23217 }, 23218 23219 /** 23220 * Copies selected text 23221 * @param {Event} e Event object 23222 */ 23223 copy: function(e) { 23224 if (this.selectionStart === this.selectionEnd) { 23225 //do not cut-copy if no selection 23226 return; 23227 } 23228 var selectedText = this.getSelectedText(), 23229 clipboardData = this._getClipboardData(e); 23230 23231 // Check for backward compatibility with old browsers 23232 if (clipboardData) { 23233 clipboardData.setData('text', selectedText); 23234 } 23235 23236 fabric.copiedText = selectedText; 23237 fabric.copiedTextStyle = this.getSelectionStyles( 23238 this.selectionStart, 23239 this.selectionEnd); 23240 e.stopImmediatePropagation(); 23241 e.preventDefault(); 23242 this._copyDone = true; 23243 }, 23244 23245 /** 23246 * Pastes text 23247 * @param {Event} e Event object 23248 */ 23249 paste: function(e) { 23250 var copiedText = null, 23251 clipboardData = this._getClipboardData(e), 23252 useCopiedStyle = true; 23253 23254 // Check for backward compatibility with old browsers 23255 if (clipboardData) { 23256 copiedText = clipboardData.getData('text').replace(/\r/g, ''); 23257 if (!fabric.copiedTextStyle || fabric.copiedText !== copiedText) { 23258 useCopiedStyle = false; 23259 } 23260 } 23261 else { 23262 copiedText = fabric.copiedText; 23263 } 23264 23265 if (copiedText) { 23266 this.insertChars(copiedText, useCopiedStyle); 23267 } 23268 e.stopImmediatePropagation(); 23269 e.preventDefault(); 23270 }, 23271 23272 /** 23273 * Cuts text 23274 * @param {Event} e Event object 23275 */ 23276 cut: function(e) { 23277 if (this.selectionStart === this.selectionEnd) { 23278 return; 23279 } 23280 23281 this.copy(e); 23282 this.removeChars(e); 23283 }, 23284 23285 /** 23286 * @private 23287 * @param {Event} e Event object 23288 * @return {Object} Clipboard data object 23289 */ 23290 _getClipboardData: function(e) { 23291 return (e && e.clipboardData) || fabric.window.clipboardData; 23292 }, 23293 23294 /** 23295 * Gets start offset of a selection 23296 * @param {Event} e Event object 23297 * @param {Boolean} isRight 23298 * @return {Number} 23299 */ 23300 getDownCursorOffset: function(e, isRight) { 23301 var selectionProp = isRight ? this.selectionEnd : this.selectionStart, 23302 cursorLocation = this.get2DCursorLocation(selectionProp), 23303 _char, lineLeftOffset, lineIndex = cursorLocation.lineIndex, 23304 textOnSameLineBeforeCursor = this._textLines[lineIndex].slice(0, cursorLocation.charIndex), 23305 textOnSameLineAfterCursor = this._textLines[lineIndex].slice(cursorLocation.charIndex), 23306 textOnNextLine = this._textLines[lineIndex + 1] || ''; 23307 23308 // if on last line, down cursor goes to end of line 23309 if (lineIndex === this._textLines.length - 1 || e.metaKey || e.keyCode === 34) { 23310 23311 // move to the end of a text 23312 return this.text.length - selectionProp; 23313 } 23314 23315 var widthOfSameLineBeforeCursor = this._getLineWidth(this.ctx, lineIndex); 23316 lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor); 23317 23318 var widthOfCharsOnSameLineBeforeCursor = lineLeftOffset; 23319 23320 for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) { 23321 _char = textOnSameLineBeforeCursor[i]; 23322 widthOfCharsOnSameLineBeforeCursor += this._getWidthOfChar(this.ctx, _char, lineIndex, i); 23323 } 23324 23325 var indexOnNextLine = this._getIndexOnNextLine( 23326 cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor); 23327 23328 return textOnSameLineAfterCursor.length + 1 + indexOnNextLine; 23329 }, 23330 23331 /** 23332 * @private 23333 */ 23334 _getIndexOnNextLine: function(cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor) { 23335 var lineIndex = cursorLocation.lineIndex + 1, 23336 widthOfNextLine = this._getLineWidth(this.ctx, lineIndex), 23337 lineLeftOffset = this._getLineLeftOffset(widthOfNextLine), 23338 widthOfCharsOnNextLine = lineLeftOffset, 23339 indexOnNextLine = 0, 23340 foundMatch; 23341 23342 for (var j = 0, jlen = textOnNextLine.length; j < jlen; j++) { 23343 23344 var _char = textOnNextLine[j], 23345 widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); 23346 23347 widthOfCharsOnNextLine += widthOfChar; 23348 23349 if (widthOfCharsOnNextLine > widthOfCharsOnSameLineBeforeCursor) { 23350 23351 foundMatch = true; 23352 23353 var leftEdge = widthOfCharsOnNextLine - widthOfChar, 23354 rightEdge = widthOfCharsOnNextLine, 23355 offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor), 23356 offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor); 23357 23358 indexOnNextLine = offsetFromRightEdge < offsetFromLeftEdge ? j + 1 : j; 23359 23360 break; 23361 } 23362 } 23363 23364 // reached end 23365 if (!foundMatch) { 23366 indexOnNextLine = textOnNextLine.length; 23367 } 23368 23369 return indexOnNextLine; 23370 }, 23371 23372 /** 23373 * Moves cursor down 23374 * @param {Event} e Event object 23375 */ 23376 moveCursorDown: function(e) { 23377 this.abortCursorAnimation(); 23378 this._currentCursorOpacity = 1; 23379 23380 var offset = this.getDownCursorOffset(e, this._selectionDirection === 'right'); 23381 23382 if (e.shiftKey) { 23383 this.moveCursorDownWithShift(offset); 23384 } 23385 else { 23386 this.moveCursorDownWithoutShift(offset); 23387 } 23388 23389 this.initDelayedCursor(); 23390 }, 23391 23392 /** 23393 * Moves cursor down without keeping selection 23394 * @param {Number} offset 23395 */ 23396 moveCursorDownWithoutShift: function(offset) { 23397 this._selectionDirection = 'right'; 23398 this.setSelectionStart(this.selectionStart + offset); 23399 this.setSelectionEnd(this.selectionStart); 23400 }, 23401 23402 /** 23403 * private 23404 */ 23405 swapSelectionPoints: function() { 23406 var swapSel = this.selectionEnd; 23407 this.setSelectionEnd(this.selectionStart); 23408 this.setSelectionStart(swapSel); 23409 }, 23410 23411 /** 23412 * Moves cursor down while keeping selection 23413 * @param {Number} offset 23414 */ 23415 moveCursorDownWithShift: function(offset) { 23416 if (this.selectionEnd === this.selectionStart) { 23417 this._selectionDirection = 'right'; 23418 } 23419 if (this._selectionDirection === 'right') { 23420 this.setSelectionEnd(this.selectionEnd + offset); 23421 } 23422 else { 23423 this.setSelectionStart(this.selectionStart + offset); 23424 } 23425 if (this.selectionEnd < this.selectionStart && this._selectionDirection === 'left') { 23426 this.swapSelectionPoints(); 23427 this._selectionDirection = 'right'; 23428 } 23429 if (this.selectionEnd > this.text.length) { 23430 this.setSelectionEnd(this.text.length); 23431 } 23432 }, 23433 23434 /** 23435 * @param {Event} e Event object 23436 * @param {Boolean} isRight 23437 * @return {Number} 23438 */ 23439 getUpCursorOffset: function(e, isRight) { 23440 var selectionProp = isRight ? this.selectionEnd : this.selectionStart, 23441 cursorLocation = this.get2DCursorLocation(selectionProp), 23442 lineIndex = cursorLocation.lineIndex; 23443 // if on first line, up cursor goes to start of line 23444 if (lineIndex === 0 || e.metaKey || e.keyCode === 33) { 23445 return selectionProp; 23446 } 23447 23448 var textOnSameLineBeforeCursor = this._textLines[lineIndex].slice(0, cursorLocation.charIndex), 23449 textOnPreviousLine = this._textLines[lineIndex - 1] || '', 23450 _char, 23451 widthOfSameLineBeforeCursor = this._getLineWidth(this.ctx, cursorLocation.lineIndex), 23452 lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor), 23453 widthOfCharsOnSameLineBeforeCursor = lineLeftOffset; 23454 23455 for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) { 23456 _char = textOnSameLineBeforeCursor[i]; 23457 widthOfCharsOnSameLineBeforeCursor += this._getWidthOfChar(this.ctx, _char, lineIndex, i); 23458 } 23459 23460 var indexOnPrevLine = this._getIndexOnPrevLine( 23461 cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor); 23462 23463 return textOnPreviousLine.length - indexOnPrevLine + textOnSameLineBeforeCursor.length; 23464 }, 23465 23466 /** 23467 * @private 23468 */ 23469 _getIndexOnPrevLine: function(cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor) { 23470 23471 var lineIndex = cursorLocation.lineIndex - 1, 23472 widthOfPreviousLine = this._getLineWidth(this.ctx, lineIndex), 23473 lineLeftOffset = this._getLineLeftOffset(widthOfPreviousLine), 23474 widthOfCharsOnPreviousLine = lineLeftOffset, 23475 indexOnPrevLine = 0, 23476 foundMatch; 23477 23478 for (var j = 0, jlen = textOnPreviousLine.length; j < jlen; j++) { 23479 23480 var _char = textOnPreviousLine[j], 23481 widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); 23482 23483 widthOfCharsOnPreviousLine += widthOfChar; 23484 23485 if (widthOfCharsOnPreviousLine > widthOfCharsOnSameLineBeforeCursor) { 23486 23487 foundMatch = true; 23488 23489 var leftEdge = widthOfCharsOnPreviousLine - widthOfChar, 23490 rightEdge = widthOfCharsOnPreviousLine, 23491 offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor), 23492 offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor); 23493 23494 indexOnPrevLine = offsetFromRightEdge < offsetFromLeftEdge ? j : (j - 1); 23495 23496 break; 23497 } 23498 } 23499 23500 // reached end 23501 if (!foundMatch) { 23502 indexOnPrevLine = textOnPreviousLine.length - 1; 23503 } 23504 23505 return indexOnPrevLine; 23506 }, 23507 23508 /** 23509 * Moves cursor up 23510 * @param {Event} e Event object 23511 */ 23512 moveCursorUp: function(e) { 23513 23514 this.abortCursorAnimation(); 23515 this._currentCursorOpacity = 1; 23516 23517 var offset = this.getUpCursorOffset(e, this._selectionDirection === 'right'); 23518 if (e.shiftKey) { 23519 this.moveCursorUpWithShift(offset); 23520 } 23521 else { 23522 this.moveCursorUpWithoutShift(offset); 23523 } 23524 23525 this.initDelayedCursor(); 23526 }, 23527 23528 /** 23529 * Moves cursor up with shift 23530 * @param {Number} offset 23531 */ 23532 moveCursorUpWithShift: function(offset) { 23533 if (this.selectionEnd === this.selectionStart) { 23534 this._selectionDirection = 'left'; 23535 } 23536 if (this._selectionDirection === 'right') { 23537 this.setSelectionEnd(this.selectionEnd - offset); 23538 } 23539 else { 23540 this.setSelectionStart(this.selectionStart - offset); 23541 } 23542 if (this.selectionEnd < this.selectionStart && this._selectionDirection === 'right') { 23543 this.swapSelectionPoints(); 23544 this._selectionDirection = 'left'; 23545 } 23546 }, 23547 23548 /** 23549 * Moves cursor up without shift 23550 * @param {Number} offset 23551 */ 23552 moveCursorUpWithoutShift: function(offset) { 23553 if (this.selectionStart === this.selectionEnd) { 23554 this.setSelectionStart(this.selectionStart - offset); 23555 } 23556 this.setSelectionEnd(this.selectionStart); 23557 23558 this._selectionDirection = 'left'; 23559 }, 23560 23561 /** 23562 * Moves cursor left 23563 * @param {Event} e Event object 23564 */ 23565 moveCursorLeft: function(e) { 23566 if (this.selectionStart === 0 && this.selectionEnd === 0) { 23567 return; 23568 } 23569 23570 this.abortCursorAnimation(); 23571 this._currentCursorOpacity = 1; 23572 23573 if (e.shiftKey) { 23574 this.moveCursorLeftWithShift(e); 23575 } 23576 else { 23577 this.moveCursorLeftWithoutShift(e); 23578 } 23579 23580 this.initDelayedCursor(); 23581 }, 23582 23583 /** 23584 * @private 23585 */ 23586 _move: function(e, prop, direction) { 23587 var propMethod = (prop === 'selectionStart' ? 'setSelectionStart' : 'setSelectionEnd'); 23588 if (e.altKey) { 23589 this[propMethod](this['findWordBoundary' + direction](this[prop])); 23590 } 23591 else if (e.metaKey || e.keyCode === 35 || e.keyCode === 36 ) { 23592 this[propMethod](this['findLineBoundary' + direction](this[prop])); 23593 } 23594 else { 23595 this[propMethod](this[prop] + (direction === 'Left' ? -1 : 1)); 23596 } 23597 }, 23598 23599 /** 23600 * @private 23601 */ 23602 _moveLeft: function(e, prop) { 23603 this._move(e, prop, 'Left'); 23604 }, 23605 23606 /** 23607 * @private 23608 */ 23609 _moveRight: function(e, prop) { 23610 this._move(e, prop, 'Right'); 23611 }, 23612 23613 /** 23614 * Moves cursor left without keeping selection 23615 * @param {Event} e 23616 */ 23617 moveCursorLeftWithoutShift: function(e) { 23618 this._selectionDirection = 'left'; 23619 23620 // only move cursor when there is no selection, 23621 // otherwise we discard it, and leave cursor on same place 23622 if (this.selectionEnd === this.selectionStart) { 23623 this._moveLeft(e, 'selectionStart'); 23624 } 23625 this.setSelectionEnd(this.selectionStart); 23626 }, 23627 23628 /** 23629 * Moves cursor left while keeping selection 23630 * @param {Event} e 23631 */ 23632 moveCursorLeftWithShift: function(e) { 23633 if (this._selectionDirection === 'right' && this.selectionStart !== this.selectionEnd) { 23634 this._moveLeft(e, 'selectionEnd'); 23635 } 23636 else { 23637 this._selectionDirection = 'left'; 23638 this._moveLeft(e, 'selectionStart'); 23639 } 23640 }, 23641 23642 /** 23643 * Moves cursor right 23644 * @param {Event} e Event object 23645 */ 23646 moveCursorRight: function(e) { 23647 if (this.selectionStart >= this.text.length && this.selectionEnd >= this.text.length) { 23648 return; 23649 } 23650 23651 this.abortCursorAnimation(); 23652 this._currentCursorOpacity = 1; 23653 23654 if (e.shiftKey) { 23655 this.moveCursorRightWithShift(e); 23656 } 23657 else { 23658 this.moveCursorRightWithoutShift(e); 23659 } 23660 23661 this.initDelayedCursor(); 23662 }, 23663 23664 /** 23665 * Moves cursor right while keeping selection 23666 * @param {Event} e 23667 */ 23668 moveCursorRightWithShift: function(e) { 23669 if (this._selectionDirection === 'left' && this.selectionStart !== this.selectionEnd) { 23670 this._moveRight(e, 'selectionStart'); 23671 } 23672 else { 23673 this._selectionDirection = 'right'; 23674 this._moveRight(e, 'selectionEnd'); 23675 } 23676 }, 23677 23678 /** 23679 * Moves cursor right without keeping selection 23680 * @param {Event} e Event object 23681 */ 23682 moveCursorRightWithoutShift: function(e) { 23683 this._selectionDirection = 'right'; 23684 23685 if (this.selectionStart === this.selectionEnd) { 23686 this._moveRight(e, 'selectionStart'); 23687 this.setSelectionEnd(this.selectionStart); 23688 } 23689 else { 23690 this.setSelectionEnd(this.selectionEnd + this.getNumNewLinesInSelectedText()); 23691 this.setSelectionStart(this.selectionEnd); 23692 } 23693 }, 23694 23695 /** 23696 * Removes characters selected by selection 23697 * @param {Event} e Event object 23698 */ 23699 removeChars: function(e) { 23700 if (this.selectionStart === this.selectionEnd) { 23701 this._removeCharsNearCursor(e); 23702 } 23703 else { 23704 this._removeCharsFromTo(this.selectionStart, this.selectionEnd); 23705 } 23706 23707 this.setSelectionEnd(this.selectionStart); 23708 23709 this._removeExtraneousStyles(); 23710 23711 this.canvas && this.canvas.renderAll(); 23712 23713 this.setCoords(); 23714 this.fire('changed'); 23715 this.canvas && this.canvas.fire('text:changed', { target: this }); 23716 }, 23717 23718 /** 23719 * @private 23720 * @param {Event} e Event object 23721 */ 23722 _removeCharsNearCursor: function(e) { 23723 if (this.selectionStart === 0) { 23724 return; 23725 } 23726 if (e.metaKey) { 23727 // remove all till the start of current line 23728 var leftLineBoundary = this.findLineBoundaryLeft(this.selectionStart); 23729 23730 this._removeCharsFromTo(leftLineBoundary, this.selectionStart); 23731 this.setSelectionStart(leftLineBoundary); 23732 } 23733 else if (e.altKey) { 23734 // remove all till the start of current word 23735 var leftWordBoundary = this.findWordBoundaryLeft(this.selectionStart); 23736 23737 this._removeCharsFromTo(leftWordBoundary, this.selectionStart); 23738 this.setSelectionStart(leftWordBoundary); 23739 } 23740 else { 23741 this._removeSingleCharAndStyle(this.selectionStart); 23742 this.setSelectionStart(this.selectionStart - 1); 23743 } 23744 } 23745 }); 23746 23747 23748 /* _TO_SVG_START_ */ 23749 (function() { 23750 var toFixed = fabric.util.toFixed, 23751 NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; 23752 23753 fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { 23754 23755 /** 23756 * @private 23757 */ 23758 _setSVGTextLineText: function(lineIndex, textSpans, height, textLeftOffset, textTopOffset, textBgRects) { 23759 if (!this._getLineStyle(lineIndex)) { 23760 fabric.Text.prototype._setSVGTextLineText.call(this, 23761 lineIndex, textSpans, height, textLeftOffset, textTopOffset); 23762 } 23763 else { 23764 this._setSVGTextLineChars( 23765 lineIndex, textSpans, height, textLeftOffset, textBgRects); 23766 } 23767 }, 23768 23769 /** 23770 * @private 23771 */ 23772 _setSVGTextLineChars: function(lineIndex, textSpans, height, textLeftOffset, textBgRects) { 23773 23774 var chars = this._textLines[lineIndex], 23775 charOffset = 0, 23776 lineLeftOffset = this._getLineLeftOffset(this._getLineWidth(this.ctx, lineIndex)) - this.width / 2, 23777 lineOffset = this._getSVGLineTopOffset(lineIndex), 23778 heightOfLine = this._getHeightOfLine(this.ctx, lineIndex); 23779 23780 for (var i = 0, len = chars.length; i < len; i++) { 23781 var styleDecl = this._getStyleDeclaration(lineIndex, i) || { }; 23782 23783 textSpans.push( 23784 this._createTextCharSpan( 23785 chars[i], styleDecl, lineLeftOffset, lineOffset.lineTop + lineOffset.offset, charOffset)); 23786 23787 var charWidth = this._getWidthOfChar(this.ctx, chars[i], lineIndex, i); 23788 23789 if (styleDecl.textBackgroundColor) { 23790 textBgRects.push( 23791 this._createTextCharBg( 23792 styleDecl, lineLeftOffset, lineOffset.lineTop, heightOfLine, charWidth, charOffset)); 23793 } 23794 23795 charOffset += charWidth; 23796 } 23797 }, 23798 23799 /** 23800 * @private 23801 */ 23802 _getSVGLineTopOffset: function(lineIndex) { 23803 var lineTopOffset = 0, lastHeight = 0; 23804 for (var j = 0; j < lineIndex; j++) { 23805 lineTopOffset += this._getHeightOfLine(this.ctx, j); 23806 } 23807 lastHeight = this._getHeightOfLine(this.ctx, j); 23808 return { 23809 lineTop: lineTopOffset, 23810 offset: (this._fontSizeMult - this._fontSizeFraction) * lastHeight / (this.lineHeight * this._fontSizeMult) 23811 }; 23812 }, 23813 23814 /** 23815 * @private 23816 */ 23817 _createTextCharBg: function(styleDecl, lineLeftOffset, lineTopOffset, heightOfLine, charWidth, charOffset) { 23818 return [ 23819 //jscs:disable validateIndentation 23820 '\t\t<rect fill="', styleDecl.textBackgroundColor, 23821 '" x="', toFixed(lineLeftOffset + charOffset, NUM_FRACTION_DIGITS), 23822 '" y="', toFixed(lineTopOffset - this.height/2, NUM_FRACTION_DIGITS), 23823 '" width="', toFixed(charWidth, NUM_FRACTION_DIGITS), 23824 '" height="', toFixed(heightOfLine / this.lineHeight, NUM_FRACTION_DIGITS), 23825 '"></rect>\n' 23826 //jscs:enable validateIndentation 23827 ].join(''); 23828 }, 23829 23830 /** 23831 * @private 23832 */ 23833 _createTextCharSpan: function(_char, styleDecl, lineLeftOffset, lineTopOffset, charOffset) { 23834 23835 var fillStyles = this.getSvgStyles.call(fabric.util.object.extend({ 23836 visible: true, 23837 fill: this.fill, 23838 stroke: this.stroke, 23839 type: 'text', 23840 getSvgFilter: fabric.Object.prototype.getSvgFilter 23841 }, styleDecl)); 23842 23843 return [ 23844 //jscs:disable validateIndentation 23845 '\t\t\t<tspan x="', toFixed(lineLeftOffset + charOffset, NUM_FRACTION_DIGITS), '" y="', 23846 toFixed(lineTopOffset - this.height/2, NUM_FRACTION_DIGITS), '" ', 23847 (styleDecl.fontFamily ? 'font-family="' + styleDecl.fontFamily.replace(/"/g, '\'') + '" ': ''), 23848 (styleDecl.fontSize ? 'font-size="' + styleDecl.fontSize + '" ': ''), 23849 (styleDecl.fontStyle ? 'font-style="' + styleDecl.fontStyle + '" ': ''), 23850 (styleDecl.fontWeight ? 'font-weight="' + styleDecl.fontWeight + '" ': ''), 23851 (styleDecl.textDecoration ? 'text-decoration="' + styleDecl.textDecoration + '" ': ''), 23852 'style="', fillStyles, '">', 23853 fabric.util.string.escapeXml(_char), 23854 '</tspan>\n' 23855 //jscs:enable validateIndentation 23856 ].join(''); 23857 } 23858 }); 23859 })(); 23860 /* _TO_SVG_END_ */ 23861 23862 23863 (function(global) { 23864 23865 'use strict'; 23866 23867 var fabric = global.fabric || (global.fabric = {}), 23868 clone = fabric.util.object.clone; 23869 23870 /** 23871 * Textbox class, based on IText, allows the user to resize the text rectangle 23872 * and wraps lines automatically. Textboxes have their Y scaling locked, the 23873 * user can only change width. Height is adjusted automatically based on the 23874 * wrapping of lines. 23875 * @class fabric.Textbox 23876 * @extends fabric.IText 23877 * @mixes fabric.Observable 23878 * @return {fabric.Textbox} thisArg 23879 * @see {@link fabric.Textbox#initialize} for constructor definition 23880 */ 23881 fabric.Textbox = fabric.util.createClass(fabric.IText, fabric.Observable, { 23882 /** 23883 * Type of an object 23884 * @type String 23885 * @default 23886 */ 23887 type: 'textbox', 23888 /** 23889 * Minimum width of textbox, in pixels. 23890 * @type Number 23891 * @default 23892 */ 23893 minWidth: 20, 23894 /** 23895 * Minimum calculated width of a textbox, in pixels. 23896 * @type Number 23897 * @default 23898 */ 23899 dynamicMinWidth: 0, 23900 /** 23901 * Cached array of text wrapping. 23902 * @type Array 23903 */ 23904 __cachedLines: null, 23905 /** 23906 * Constructor. Some scaling related property values are forced. Visibility 23907 * of controls is also fixed; only the rotation and width controls are 23908 * made available. 23909 * @param {String} text Text string 23910 * @param {Object} [options] Options object 23911 * @return {fabric.Textbox} thisArg 23912 */ 23913 initialize: function(text, options) { 23914 this.ctx = fabric.util.createCanvasElement().getContext('2d'); 23915 23916 this.callSuper('initialize', text, options); 23917 this.set({ 23918 lockUniScaling: false, 23919 lockScalingY: true, 23920 lockScalingFlip: true, 23921 hasBorders: true 23922 }); 23923 this.setControlsVisibility(fabric.Textbox.getTextboxControlVisibility()); 23924 23925 // add width to this list of props that effect line wrapping. 23926 this._dimensionAffectingProps.width = true; 23927 }, 23928 23929 /** 23930 * Unlike superclass's version of this function, Textbox does not update 23931 * its width. 23932 * @param {CanvasRenderingContext2D} ctx Context to use for measurements 23933 * @private 23934 * @override 23935 */ 23936 _initDimensions: function(ctx) { 23937 if (this.__skipDimension) { 23938 return; 23939 } 23940 23941 if (!ctx) { 23942 ctx = fabric.util.createCanvasElement().getContext('2d'); 23943 this._setTextStyles(ctx); 23944 } 23945 23946 // clear dynamicMinWidth as it will be different after we re-wrap line 23947 this.dynamicMinWidth = 0; 23948 23949 // wrap lines 23950 this._textLines = this._splitTextIntoLines(); 23951 23952 // if after wrapping, the width is smaller than dynamicMinWidth, change the width and re-wrap 23953 if (this.dynamicMinWidth > this.width) { 23954 this._set('width', this.dynamicMinWidth); 23955 } 23956 23957 // clear cache and re-calculate height 23958 this._clearCache(); 23959 this.height = this._getTextHeight(ctx); 23960 }, 23961 23962 /** 23963 * Generate an object that translates the style object so that it is 23964 * broken up by visual lines (new lines and automatic wrapping). 23965 * The original text styles object is broken up by actual lines (new lines only), 23966 * which is only sufficient for Text / IText 23967 * @private 23968 */ 23969 _generateStyleMap: function() { 23970 var realLineCount = 0, 23971 realLineCharCount = 0, 23972 charCount = 0, 23973 map = {}; 23974 23975 for (var i = 0; i < this._textLines.length; i++) { 23976 if (this.text[charCount] === '\n') { 23977 realLineCharCount = 0; 23978 charCount++; 23979 realLineCount++; 23980 } 23981 else if (this.text[charCount] === ' ') { 23982 // this case deals with space's that are removed from end of lines when wrapping 23983 realLineCharCount++; 23984 charCount++; 23985 } 23986 23987 map[i] = { line: realLineCount, offset: realLineCharCount }; 23988 23989 charCount += this._textLines[i].length; 23990 realLineCharCount += this._textLines[i].length; 23991 } 23992 23993 return map; 23994 }, 23995 23996 /** 23997 * @param {Number} lineIndex 23998 * @param {Number} charIndex 23999 * @param {Boolean} [returnCloneOrEmpty=false] 24000 * @private 24001 */ 24002 _getStyleDeclaration: function(lineIndex, charIndex, returnCloneOrEmpty) { 24003 if (this._styleMap) { 24004 var map = this._styleMap[lineIndex]; 24005 if (!map) { 24006 return returnCloneOrEmpty ? { } : null; 24007 } 24008 lineIndex = map.line; 24009 charIndex = map.offset + charIndex; 24010 } 24011 return this.callSuper('_getStyleDeclaration', lineIndex, charIndex, returnCloneOrEmpty); 24012 }, 24013 24014 /** 24015 * @param {Number} lineIndex 24016 * @param {Number} charIndex 24017 * @param {Object} style 24018 * @private 24019 */ 24020 _setStyleDeclaration: function(lineIndex, charIndex, style) { 24021 var map = this._styleMap[lineIndex]; 24022 lineIndex = map.line; 24023 charIndex = map.offset + charIndex; 24024 24025 this.styles[lineIndex][charIndex] = style; 24026 }, 24027 24028 /** 24029 * @param {Number} lineIndex 24030 * @param {Number} charIndex 24031 * @private 24032 */ 24033 _deleteStyleDeclaration: function(lineIndex, charIndex) { 24034 var map = this._styleMap[lineIndex]; 24035 lineIndex = map.line; 24036 charIndex = map.offset + charIndex; 24037 24038 delete this.styles[lineIndex][charIndex]; 24039 }, 24040 24041 /** 24042 * @param {Number} lineIndex 24043 * @private 24044 */ 24045 _getLineStyle: function(lineIndex) { 24046 var map = this._styleMap[lineIndex]; 24047 return this.styles[map.line]; 24048 }, 24049 24050 /** 24051 * @param {Number} lineIndex 24052 * @param {Object} style 24053 * @private 24054 */ 24055 _setLineStyle: function(lineIndex, style) { 24056 var map = this._styleMap[lineIndex]; 24057 this.styles[map.line] = style; 24058 }, 24059 24060 /** 24061 * @param {Number} lineIndex 24062 * @private 24063 */ 24064 _deleteLineStyle: function(lineIndex) { 24065 var map = this._styleMap[lineIndex]; 24066 delete this.styles[map.line]; 24067 }, 24068 24069 /** 24070 * Wraps text using the 'width' property of Textbox. First this function 24071 * splits text on newlines, so we preserve newlines entered by the user. 24072 * Then it wraps each line using the width of the Textbox by calling 24073 * _wrapLine(). 24074 * @param {CanvasRenderingContext2D} ctx Context to use for measurements 24075 * @param {String} text The string of text that is split into lines 24076 * @returns {Array} Array of lines 24077 */ 24078 _wrapText: function(ctx, text) { 24079 var lines = text.split(this._reNewline), wrapped = [], i; 24080 24081 for (i = 0; i < lines.length; i++) { 24082 wrapped = wrapped.concat(this._wrapLine(ctx, lines[i], i)); 24083 } 24084 24085 return wrapped; 24086 }, 24087 24088 /** 24089 * Helper function to measure a string of text, given its lineIndex and charIndex offset 24090 * 24091 * @param {CanvasRenderingContext2D} ctx 24092 * @param {String} text 24093 * @param {number} lineIndex 24094 * @param {number} charOffset 24095 * @returns {number} 24096 * @private 24097 */ 24098 _measureText: function(ctx, text, lineIndex, charOffset) { 24099 var width = 0; 24100 charOffset = charOffset || 0; 24101 24102 for (var i = 0, len = text.length; i < len; i++) { 24103 width += this._getWidthOfChar(ctx, text[i], lineIndex, i + charOffset); 24104 } 24105 24106 return width; 24107 }, 24108 24109 /** 24110 * Wraps a line of text using the width of the Textbox and a context. 24111 * @param {CanvasRenderingContext2D} ctx Context to use for measurements 24112 * @param {String} text The string of text to split into lines 24113 * @param {Number} lineIndex 24114 * @returns {Array} Array of line(s) into which the given text is wrapped 24115 * to. 24116 */ 24117 _wrapLine: function(ctx, text, lineIndex) { 24118 var lineWidth = 0, 24119 lines = [], 24120 line = '', 24121 words = text.split(' '), 24122 word = '', 24123 offset = 0, 24124 infix = ' ', 24125 wordWidth = 0, 24126 infixWidth = 0, 24127 largestWordWidth = 0, 24128 lineJustStarted = true; 24129 24130 for (var i = 0; i < words.length; i++) { 24131 word = words[i]; 24132 wordWidth = this._measureText(ctx, word, lineIndex, offset); 24133 24134 offset += word.length; 24135 24136 lineWidth += infixWidth + wordWidth; 24137 24138 if (lineWidth >= this.width && !lineJustStarted) { 24139 lines.push(line); 24140 line = ''; 24141 lineWidth = wordWidth; 24142 lineJustStarted = true; 24143 } 24144 24145 if (!lineJustStarted) { 24146 line += infix; 24147 } 24148 line += word; 24149 24150 infixWidth = this._measureText(ctx, infix, lineIndex, offset); 24151 offset++; 24152 lineJustStarted = false; 24153 // keep track of largest word 24154 if (wordWidth > largestWordWidth) { 24155 largestWordWidth = wordWidth; 24156 } 24157 } 24158 24159 i && lines.push(line); 24160 24161 if (largestWordWidth > this.dynamicMinWidth) { 24162 this.dynamicMinWidth = largestWordWidth; 24163 } 24164 24165 return lines; 24166 }, 24167 /** 24168 * Gets lines of text to render in the Textbox. This function calculates 24169 * text wrapping on the fly everytime it is called. 24170 * @returns {Array} Array of lines in the Textbox. 24171 * @override 24172 */ 24173 _splitTextIntoLines: function() { 24174 var originalAlign = this.textAlign; 24175 this.ctx.save(); 24176 this._setTextStyles(this.ctx); 24177 this.textAlign = 'left'; 24178 var lines = this._wrapText(this.ctx, this.text); 24179 this.textAlign = originalAlign; 24180 this.ctx.restore(); 24181 this._textLines = lines; 24182 this._styleMap = this._generateStyleMap(); 24183 return lines; 24184 }, 24185 24186 /** 24187 * When part of a group, we don't want the Textbox's scale to increase if 24188 * the group's increases. That's why we reduce the scale of the Textbox by 24189 * the amount that the group's increases. This is to maintain the effective 24190 * scale of the Textbox at 1, so that font-size values make sense. Otherwise 24191 * the same font-size value would result in different actual size depending 24192 * on the value of the scale. 24193 * @param {String} key 24194 * @param {Any} value 24195 */ 24196 setOnGroup: function(key, value) { 24197 if (key === 'scaleX') { 24198 this.set('scaleX', Math.abs(1 / value)); 24199 this.set('width', (this.get('width') * value) / 24200 (typeof this.__oldScaleX === 'undefined' ? 1 : this.__oldScaleX)); 24201 this.__oldScaleX = value; 24202 } 24203 }, 24204 24205 /** 24206 * Returns 2d representation (lineIndex and charIndex) of cursor (or selection start). 24207 * Overrides the superclass function to take into account text wrapping. 24208 * 24209 * @param {Number} [selectionStart] Optional index. When not given, current selectionStart is used. 24210 */ 24211 get2DCursorLocation: function(selectionStart) { 24212 if (typeof selectionStart === 'undefined') { 24213 selectionStart = this.selectionStart; 24214 } 24215 24216 var numLines = this._textLines.length, 24217 removed = 0; 24218 24219 for (var i = 0; i < numLines; i++) { 24220 var line = this._textLines[i], 24221 lineLen = line.length; 24222 24223 if (selectionStart <= removed + lineLen) { 24224 return { 24225 lineIndex: i, 24226 charIndex: selectionStart - removed 24227 }; 24228 } 24229 24230 removed += lineLen; 24231 24232 if (this.text[removed] === '\n' || this.text[removed] === ' ') { 24233 removed++; 24234 } 24235 } 24236 24237 return { 24238 lineIndex: numLines - 1, 24239 charIndex: this._textLines[numLines - 1].length 24240 }; 24241 }, 24242 24243 /** 24244 * Overrides superclass function and uses text wrapping data to get cursor 24245 * boundary offsets instead of the array of chars. 24246 * @param {Array} chars Unused 24247 * @param {String} typeOfBoundaries Can be 'cursor' or 'selection' 24248 * @returns {Object} Object with 'top', 'left', and 'lineLeft' properties set. 24249 */ 24250 _getCursorBoundariesOffsets: function(chars, typeOfBoundaries) { 24251 var topOffset = 0, 24252 leftOffset = 0, 24253 cursorLocation = this.get2DCursorLocation(), 24254 lineChars = this._textLines[cursorLocation.lineIndex].split(''), 24255 lineLeftOffset = this._getLineLeftOffset(this._getLineWidth(this.ctx, cursorLocation.lineIndex)); 24256 24257 for (var i = 0; i < cursorLocation.charIndex; i++) { 24258 leftOffset += this._getWidthOfChar(this.ctx, lineChars[i], cursorLocation.lineIndex, i); 24259 } 24260 24261 for (i = 0; i < cursorLocation.lineIndex; i++) { 24262 topOffset += this._getHeightOfLine(this.ctx, i); 24263 } 24264 24265 if (typeOfBoundaries === 'cursor') { 24266 topOffset += (1 - this._fontSizeFraction) * this._getHeightOfLine(this.ctx, cursorLocation.lineIndex) 24267 / this.lineHeight - this.getCurrentCharFontSize(cursorLocation.lineIndex, cursorLocation.charIndex) 24268 * (1 - this._fontSizeFraction); 24269 } 24270 24271 return { 24272 top: topOffset, 24273 left: leftOffset, 24274 lineLeft: lineLeftOffset 24275 }; 24276 }, 24277 24278 getMinWidth: function() { 24279 return Math.max(this.minWidth, this.dynamicMinWidth); 24280 }, 24281 24282 /** 24283 * Returns object representation of an instance 24284 * @method toObject 24285 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 24286 * @return {Object} object representation of an instance 24287 */ 24288 toObject: function(propertiesToInclude) { 24289 return fabric.util.object.extend(this.callSuper('toObject', propertiesToInclude), { 24290 minWidth: this.minWidth 24291 }); 24292 } 24293 }); 24294 /** 24295 * Returns fabric.Textbox instance from an object representation 24296 * @static 24297 * @memberOf fabric.Textbox 24298 * @param {Object} object Object to create an instance from 24299 * @return {fabric.Textbox} instance of fabric.Textbox 24300 */ 24301 fabric.Textbox.fromObject = function(object) { 24302 return new fabric.Textbox(object.text, clone(object)); 24303 }; 24304 /** 24305 * Returns the default controls visibility required for Textboxes. 24306 * @returns {Object} 24307 */ 24308 fabric.Textbox.getTextboxControlVisibility = function() { 24309 return { 24310 tl: false, 24311 tr: false, 24312 br: false, 24313 bl: false, 24314 ml: true, 24315 mt: false, 24316 mr: true, 24317 mb: false, 24318 mtr: true 24319 }; 24320 }; 24321 /** 24322 * Contains all fabric.Textbox objects that have been created 24323 * @static 24324 * @memberOf fabric.Textbox 24325 * @type Array 24326 */ 24327 fabric.Textbox.instances = []; 24328 })(typeof exports !== 'undefined' ? exports : this); 24329 24330 24331 (function() { 24332 24333 /** 24334 * Override _setObjectScale and add Textbox specific resizing behavior. Resizing 24335 * a Textbox doesn't scale text, it only changes width and makes text wrap automatically. 24336 */ 24337 var setObjectScaleOverridden = fabric.Canvas.prototype._setObjectScale; 24338 24339 fabric.Canvas.prototype._setObjectScale = function(localMouse, transform, 24340 lockScalingX, lockScalingY, by, lockScalingFlip, _dim) { 24341 24342 var t = transform.target; 24343 if (t instanceof fabric.Textbox) { 24344 var w = t.width * ((localMouse.x / transform.scaleX) / (t.width + t.strokeWidth)); 24345 if (w >= t.getMinWidth()) { 24346 t.set('width', w); 24347 } 24348 } 24349 else { 24350 setObjectScaleOverridden.call(fabric.Canvas.prototype, localMouse, transform, 24351 lockScalingX, lockScalingY, by, lockScalingFlip, _dim); 24352 } 24353 }; 24354 24355 /** 24356 * Sets controls of this group to the Textbox's special configuration if 24357 * one is present in the group. Deletes _controlsVisibility otherwise, so that 24358 * it gets initialized to default value at runtime. 24359 */ 24360 fabric.Group.prototype._refreshControlsVisibility = function() { 24361 if (typeof fabric.Textbox === 'undefined') { 24362 return; 24363 } 24364 for (var i = this._objects.length; i--;) { 24365 if (this._objects[i] instanceof fabric.Textbox) { 24366 this.setControlsVisibility(fabric.Textbox.getTextboxControlVisibility()); 24367 return; 24368 } 24369 } 24370 }; 24371 24372 var clone = fabric.util.object.clone; 24373 24374 fabric.util.object.extend(fabric.Textbox.prototype, /** @lends fabric.IText.prototype */ { 24375 /** 24376 * @private 24377 */ 24378 _removeExtraneousStyles: function() { 24379 for (var prop in this._styleMap) { 24380 if (!this._textLines[prop]) { 24381 delete this.styles[this._styleMap[prop].line]; 24382 } 24383 } 24384 }, 24385 24386 /** 24387 * Inserts style object for a given line/char index 24388 * @param {Number} lineIndex Index of a line 24389 * @param {Number} charIndex Index of a char 24390 * @param {Object} [style] Style object to insert, if given 24391 */ 24392 insertCharStyleObject: function(lineIndex, charIndex, style) { 24393 // adjust lineIndex and charIndex 24394 var map = this._styleMap[lineIndex]; 24395 lineIndex = map.line; 24396 charIndex = map.offset + charIndex; 24397 24398 fabric.IText.prototype.insertCharStyleObject.apply(this, [lineIndex, charIndex, style]); 24399 }, 24400 24401 /** 24402 * Inserts new style object 24403 * @param {Number} lineIndex Index of a line 24404 * @param {Number} charIndex Index of a char 24405 * @param {Boolean} isEndOfLine True if it's end of line 24406 */ 24407 insertNewlineStyleObject: function(lineIndex, charIndex, isEndOfLine) { 24408 // adjust lineIndex and charIndex 24409 var map = this._styleMap[lineIndex]; 24410 lineIndex = map.line; 24411 charIndex = map.offset + charIndex; 24412 24413 fabric.IText.prototype.insertNewlineStyleObject.apply(this, [lineIndex, charIndex, isEndOfLine]); 24414 }, 24415 24416 /** 24417 * Shifts line styles up or down. This function is slightly different than the one in 24418 * itext_behaviour as it takes into account the styleMap. 24419 * 24420 * @param {Number} lineIndex Index of a line 24421 * @param {Number} offset Can be -1 or +1 24422 */ 24423 shiftLineStyles: function(lineIndex, offset) { 24424 // shift all line styles by 1 upward 24425 var clonedStyles = clone(this.styles), 24426 map = this._styleMap[lineIndex]; 24427 24428 // adjust line index 24429 lineIndex = map.line; 24430 24431 for (var line in this.styles) { 24432 var numericLine = parseInt(line, 10); 24433 24434 if (numericLine > lineIndex) { 24435 this.styles[numericLine + offset] = clonedStyles[numericLine]; 24436 24437 if (!clonedStyles[numericLine - offset]) { 24438 delete this.styles[numericLine]; 24439 } 24440 } 24441 } 24442 //TODO: evaluate if delete old style lines with offset -1 24443 }, 24444 24445 /** 24446 * Figure out programatically the text on previous actual line (actual = separated by \n); 24447 * 24448 * @param {Number} lIndex 24449 * @returns {String} 24450 * @private 24451 */ 24452 _getTextOnPreviousLine: function(lIndex) { 24453 var textOnPreviousLine = this._textLines[lIndex - 1]; 24454 24455 while (this._styleMap[lIndex - 2] && this._styleMap[lIndex - 2].line === this._styleMap[lIndex - 1].line) { 24456 textOnPreviousLine = this._textLines[lIndex - 2] + textOnPreviousLine; 24457 24458 lIndex--; 24459 } 24460 24461 return textOnPreviousLine; 24462 }, 24463 24464 /** 24465 * Removes style object 24466 * @param {Boolean} isBeginningOfLine True if cursor is at the beginning of line 24467 * @param {Number} [index] Optional index. When not given, current selectionStart is used. 24468 */ 24469 removeStyleObject: function(isBeginningOfLine, index) { 24470 24471 var cursorLocation = this.get2DCursorLocation(index), 24472 map = this._styleMap[cursorLocation.lineIndex], 24473 lineIndex = map.line, 24474 charIndex = map.offset + cursorLocation.charIndex; 24475 this._removeStyleObject(isBeginningOfLine, cursorLocation, lineIndex, charIndex); 24476 } 24477 }); 24478 })(); 24479 24480 24481 (function() { 24482 var override = fabric.IText.prototype._getNewSelectionStartFromOffset; 24483 /** 24484 * Overrides the IText implementation and adjusts character index as there is not always a linebreak 24485 * 24486 * @param {Number} mouseOffset 24487 * @param {Number} prevWidth 24488 * @param {Number} width 24489 * @param {Number} index 24490 * @param {Number} jlen 24491 * @returns {Number} 24492 */ 24493 fabric.IText.prototype._getNewSelectionStartFromOffset = function(mouseOffset, prevWidth, width, index, jlen) { 24494 index = override.call(this, mouseOffset, prevWidth, width, index, jlen); 24495 24496 // the index passed into the function is padded by the amount of lines from _textLines (to account for \n) 24497 // we need to remove this padding, and pad it by actual lines, and / or spaces that are meant to be there 24498 var tmp = 0, 24499 removed = 0; 24500 24501 // account for removed characters 24502 for (var i = 0; i < this._textLines.length; i++) { 24503 tmp += this._textLines[i].length; 24504 24505 if (tmp + removed >= index) { 24506 break; 24507 } 24508 24509 if (this.text[tmp + removed] === '\n' || this.text[tmp + removed] === ' ') { 24510 removed++; 24511 } 24512 } 24513 24514 return index - i + removed; 24515 }; 24516 })(); 24517 24518 24519 (function() { 24520 24521 if (typeof document !== 'undefined' && typeof window !== 'undefined') { 24522 return; 24523 } 24524 24525 var DOMParser = require('xmldom').DOMParser, 24526 URL = require('url'), 24527 HTTP = require('http'), 24528 HTTPS = require('https'), 24529 24530 Canvas = require('canvas'), 24531 Image = require('canvas').Image; 24532 24533 /** @private */ 24534 function request(url, encoding, callback) { 24535 var oURL = URL.parse(url); 24536 24537 // detect if http or https is used 24538 if ( !oURL.port ) { 24539 oURL.port = ( oURL.protocol.indexOf('https:') === 0 ) ? 443 : 80; 24540 } 24541 24542 // assign request handler based on protocol 24543 var reqHandler = (oURL.protocol.indexOf('https:') === 0 ) ? HTTPS : HTTP, 24544 req = reqHandler.request({ 24545 hostname: oURL.hostname, 24546 port: oURL.port, 24547 path: oURL.path, 24548 method: 'GET' 24549 }, function(response) { 24550 var body = ''; 24551 if (encoding) { 24552 response.setEncoding(encoding); 24553 } 24554 response.on('end', function () { 24555 callback(body); 24556 }); 24557 response.on('data', function (chunk) { 24558 if (response.statusCode === 200) { 24559 body += chunk; 24560 } 24561 }); 24562 }); 24563 24564 req.on('error', function(err) { 24565 if (err.errno === process.ECONNREFUSED) { 24566 fabric.log('ECONNREFUSED: connection refused to ' + oURL.hostname + ':' + oURL.port); 24567 } 24568 else { 24569 fabric.log(err.message); 24570 } 24571 callback(null); 24572 }); 24573 24574 req.end(); 24575 } 24576 24577 /** @private */ 24578 function requestFs(path, callback) { 24579 var fs = require('fs'); 24580 fs.readFile(path, function (err, data) { 24581 if (err) { 24582 fabric.log(err); 24583 throw err; 24584 } 24585 else { 24586 callback(data); 24587 } 24588 }); 24589 } 24590 24591 fabric.util.loadImage = function(url, callback, context) { 24592 function createImageAndCallBack(data) { 24593 if (data) { 24594 img.src = new Buffer(data, 'binary'); 24595 // preserving original url, which seems to be lost in node-canvas 24596 img._src = url; 24597 callback && callback.call(context, img); 24598 } 24599 else { 24600 img = null; 24601 callback && callback.call(context, null, true); 24602 } 24603 } 24604 var img = new Image(); 24605 if (url && (url instanceof Buffer || url.indexOf('data') === 0)) { 24606 img.src = img._src = url; 24607 callback && callback.call(context, img); 24608 } 24609 else if (url && url.indexOf('http') !== 0) { 24610 requestFs(url, createImageAndCallBack); 24611 } 24612 else if (url) { 24613 request(url, 'binary', createImageAndCallBack); 24614 } 24615 else { 24616 callback && callback.call(context, url); 24617 } 24618 }; 24619 24620 fabric.loadSVGFromURL = function(url, callback, reviver) { 24621 url = url.replace(/^\n\s*/, '').replace(/\?.*$/, '').trim(); 24622 if (url.indexOf('http') !== 0) { 24623 requestFs(url, function(body) { 24624 fabric.loadSVGFromString(body.toString(), callback, reviver); 24625 }); 24626 } 24627 else { 24628 request(url, '', function(body) { 24629 fabric.loadSVGFromString(body, callback, reviver); 24630 }); 24631 } 24632 }; 24633 24634 fabric.loadSVGFromString = function(string, callback, reviver) { 24635 var doc = new DOMParser().parseFromString(string); 24636 fabric.parseSVGDocument(doc.documentElement, function(results, options) { 24637 callback && callback(results, options); 24638 }, reviver); 24639 }; 24640 24641 fabric.util.getScript = function(url, callback) { 24642 request(url, '', function(body) { 24643 eval(body); 24644 callback && callback(); 24645 }); 24646 }; 24647 24648 fabric.Image.fromObject = function(object, callback) { 24649 fabric.util.loadImage(object.src, function(img) { 24650 var oImg = new fabric.Image(img); 24651 24652 oImg._initConfig(object); 24653 oImg._initFilters(object.filters, function(filters) { 24654 oImg.filters = filters || [ ]; 24655 oImg._initFilters(object.resizeFilters, function(resizeFilters) { 24656 oImg.resizeFilters = resizeFilters || [ ]; 24657 callback && callback(oImg); 24658 }); 24659 }); 24660 }); 24661 }; 24662 /** 24663 * Only available when running fabric on node.js 24664 * @param {Number} width Canvas width 24665 * @param {Number} height Canvas height 24666 * @param {Object} [options] Options to pass to FabricCanvas. 24667 * @param {Object} [nodeCanvasOptions] Options to pass to NodeCanvas. 24668 * @return {Object} wrapped canvas instance 24669 */ 24670 fabric.createCanvasForNode = function(width, height, options, nodeCanvasOptions) { 24671 nodeCanvasOptions = nodeCanvasOptions || options; 24672 24673 var canvasEl = fabric.document.createElement('canvas'), 24674 nodeCanvas = new Canvas(width || 600, height || 600, nodeCanvasOptions); 24675 24676 // jsdom doesn't create style on canvas element, so here be temp. workaround 24677 canvasEl.style = { }; 24678 24679 canvasEl.width = nodeCanvas.width; 24680 canvasEl.height = nodeCanvas.height; 24681 24682 var FabricCanvas = fabric.Canvas || fabric.StaticCanvas, 24683 fabricCanvas = new FabricCanvas(canvasEl, options); 24684 24685 fabricCanvas.contextContainer = nodeCanvas.getContext('2d'); 24686 fabricCanvas.nodeCanvas = nodeCanvas; 24687 fabricCanvas.Font = Canvas.Font; 24688 24689 return fabricCanvas; 24690 }; 24691 24692 /** @ignore */ 24693 fabric.StaticCanvas.prototype.createPNGStream = function() { 24694 return this.nodeCanvas.createPNGStream(); 24695 }; 24696 24697 fabric.StaticCanvas.prototype.createJPEGStream = function(opts) { 24698 return this.nodeCanvas.createJPEGStream(opts); 24699 }; 24700 24701 var origSetWidth = fabric.StaticCanvas.prototype.setWidth; 24702 fabric.StaticCanvas.prototype.setWidth = function(width, options) { 24703 origSetWidth.call(this, width, options); 24704 this.nodeCanvas.width = width; 24705 return this; 24706 }; 24707 if (fabric.Canvas) { 24708 fabric.Canvas.prototype.setWidth = fabric.StaticCanvas.prototype.setWidth; 24709 } 24710 24711 var origSetHeight = fabric.StaticCanvas.prototype.setHeight; 24712 fabric.StaticCanvas.prototype.setHeight = function(height, options) { 24713 origSetHeight.call(this, height, options); 24714 this.nodeCanvas.height = height; 24715 return this; 24716 }; 24717 if (fabric.Canvas) { 24718 fabric.Canvas.prototype.setHeight = fabric.StaticCanvas.prototype.setHeight; 24719 } 24720 24721 })(); 24722 24723